Documentation
// Rust-oracle - Rust binding for Oracle database
//
// URL: https://github.com/kubo/rust-oracle
//
//-----------------------------------------------------------------------------
// Copyright (c) 2017-2022 Kubo Takehiro <kubo@jiubao.org>. All rights reserved.
// This program is free software: you can modify it and/or redistribute it
// under the terms of:
//
// (i)  the Universal Permissive License v 1.0 or at your option, any
//      later version (http://oss.oracle.com/licenses/upl); and/or
//
// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
//-----------------------------------------------------------------------------

#[cfg(feature = "stmt_without_lifetime")]
use oracle_procmacro::remove_stmt_lifetime;
use std::borrow::ToOwned;
use std::fmt;
#[cfg(not(feature = "stmt_without_lifetime"))]
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use std::ptr;
use std::rc::Rc;
use std::sync::atomic::{AtomicU32, Ordering};

use crate::binding::*;
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_odpi_str;
use crate::to_rust_str;
use crate::Connection;
use crate::Context;
use crate::Error;
use crate::Result;
use crate::ResultSet;
use crate::Row;
use crate::RowValue;
use crate::SqlValue;
#[cfg(doc)]
use std::io::Read;

// https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-A251CF91-EB9F-4DBC-8BB8-FB5EA92C20DE
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,
        }
    }
}

/// A builder to create a [`Statement`][] with various configuration
pub struct StatementBuilder<'conn, 'sql> {
    conn: &'conn Connection,
    sql: &'sql str,
    query_params: QueryParams,
    scrollable: bool,
    tag: String,
    exclude_from_cache: bool,
}

#[cfg_attr(feature = "stmt_without_lifetime", remove_stmt_lifetime)]
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,
        }
    }

    /// Changes the array size used for performing fetches.
    ///
    /// This specifies the number of rows allocated before performing
    /// fetches. The default value is 100. Higher value reduces
    /// the number of network round trips to fetch rows but requires
    /// more memory. The preferable value depends on the query and
    /// the environment.
    ///
    /// If the query returns only onw row, it is better to change
    /// size to one.
    ///
    /// ```
    /// # use oracle::Error;
    /// # use oracle::test_util;
    /// # let conn = test_util::connect()?;
    /// let mut stmt = conn
    ///     .statement("select StringCol from TestStrings where IntCol = :1")
    ///     .fetch_array_size(1)
    ///     .build()?;
    /// assert_eq!(stmt.query_row_as::<String>(&[&1])?, "String 1");
    /// assert_eq!(stmt.query_row_as::<String>(&[&2])?, "String 2");
    /// # Ok::<(), Error>(())
    /// ```
    pub fn fetch_array_size<'a>(&'a mut self, size: u32) -> &'a mut StatementBuilder<'conn, 'sql> {
        self.query_params.fetch_array_size = size;
        self
    }

    /// The number of rows that will be prefetched by the Oracle Client
    /// library when a query is executed. The default value is
    /// DPI_DEFAULT_PREFETCH_ROWS (2). Increasing this value may reduce
    /// the number of round-trips to the database that are required in
    /// order to fetch rows, but at the cost of increasing memory
    /// requirements.
    /// Setting this value to 0 will disable prefetch completely,
    /// which may be useful when the timing for fetching rows must be
    /// controlled by the caller.
    pub fn prefetch_rows<'a>(&'a mut self, size: u32) -> &'a mut StatementBuilder<'conn, 'sql> {
        self.query_params.prefetch_rows = Some(size);
        self
    }

    /// Enables lob data types to be fetched or bound as [`Clob`], [`Nclob`] or [`Blob`].
    ///
    /// Lob data types are internally bound as string or bytes by default.
    /// It is proper for small data but not for big data. That's because
    /// when a lob contains 1 gigabyte data, the whole data are copied to the client
    /// and consume 1 gigabyte or more memory. When `lob_locator` is set and
    /// a column is fetched as [`Clob`], data are copied using [`Read::read`].
    ///
    /// # Examples
    ///
    /// ```
    /// # use oracle::Connection;
    /// # use oracle::Error;
    /// # use oracle::sql_type::Clob;
    /// # use oracle::test_util;
    /// # use std::io::{Read, Write};
    /// # let conn = test_util::connect()?;
    /// # conn.execute("delete from TestClobs", &[])?;
    /// # conn.execute("insert into TestClobs values (:1, :2)", &[&1i32, &"clob data"])?;
    /// # let mut out = vec![0u8; 0];
    /// let mut stmt = conn
    ///     .statement("select ClobCol from TestClobs where IntCol = :1")
    ///     .lob_locator()
    ///     .build()?;
    /// let mut clob = stmt.query_row_as::<Clob>(&[&1i32])?;
    ///
    /// // Copy contents of clob using 1MB buffer.
    /// let mut buf = vec![0u8; 1 * 1024 * 1024];
    /// loop {
    ///   let size = clob.read(&mut buf)?;
    ///   if size == 0 {
    ///     break;
    ///   }
    ///   out.write(&buf[0..size]);
    /// }
    /// # Ok::<(), Box::<dyn std::error::Error>>(())
    /// ```
    pub fn lob_locator<'a>(&'a mut self) -> &'a mut StatementBuilder<'conn, 'sql> {
        self.query_params.lob_bind_type = LobBindType::Locator;
        self
    }

    // make the visibility public when scrollable cursors is supported.
    fn scrollable<'a>(&'a mut self, scrollable: bool) -> &'a mut StatementBuilder<'conn, 'sql> {
        self.scrollable = scrollable;
        self
    }

    /// Specifies the key to be used for searching for the statement in the statement cache.
    /// If the key is not found, the SQL text specified by [`Connection::statement`] is used
    /// to create a statement.
    ///
    /// # Examples
    ///
    /// ```
    /// # use oracle::Error;
    /// # use oracle::test_util;
    /// # let conn = test_util::connect()?;
    ///
    /// // When both SQL text and a tag are specifed and the tag is not found
    /// // in the statement cache, the SQL text is used to make a statement.
    /// // The statement is backed to the cache with the tag when
    /// // it is closed.
    /// let mut stmt = conn.statement("select 1 from dual").tag("query one").build()?;
    /// assert_eq!(stmt.query_row_as::<i32>(&[])?, 1);
    /// stmt.close()?;
    ///
    /// // When only a tag is specified and the tag is found in the cache,
    /// // the statement with the tag is returned.
    /// let mut stmt = conn.statement("").tag("query one").build()?;
    /// assert_eq!(stmt.query_row_as::<i32>(&[])?, 1);
    /// stmt.close()?;
    ///
    /// // When only a tag is specified and the tag isn't found in the cache,
    /// // ORA-24431 is returned.
    /// let err = conn.statement("").tag("not existing tag").build().unwrap_err();
    /// match err {
    ///   Error::OciError(err) if err.code() == 24431 => {
    ///     // ORA-24431: Statement does not exist in the cache
    ///   },
    ///   _ => panic!("unexpected err {:?}", err),
    /// }
    ///
    /// // WARNING: The SQL statement is not checked when the tag is found.
    /// let mut stmt = conn.statement("select 2 from dual").tag("query one").build()?;
    /// // The result must be 2 if the SQL text is used. However it is 1
    /// // because the statement tagged with "query one" is "select 1 from dual".
    /// assert_eq!(stmt.query_row_as::<i32>(&[])?, 1);
    /// stmt.close()?;
    ///
    /// # // test whether the statement is tagged when it is closed by drop.
    /// # {
    /// #    let mut stmt = conn.statement("select 2 from dual").tag("query two").build()?;
    /// #    assert_eq!(stmt.query_row_as::<i32>(&[])?, 2);
    /// #    // stmt is dropped here.
    /// # }
    /// # let mut stmt = conn.statement("").tag("query two").build()?;
    /// # assert_eq!(stmt.query_row_as::<i32>(&[])?, 2);
    /// # Ok::<(), Error>(())
    /// ```
    pub fn tag<'a, T>(&'a mut self, tag_name: T) -> &'a mut StatementBuilder<'conn, 'sql>
    where
        T: Into<String>,
    {
        self.tag = tag_name.into();
        self
    }

    /// Excludes the statement from the cache even when stmt_cache_size is not zero.
    pub fn exclude_from_cache<'a>(&'a mut self) -> &'a mut StatementBuilder<'conn, 'sql> {
        self.exclude_from_cache = true;
        self
    }

    pub fn build(&self) -> Result<Statement<'conn>> {
        Statement::new(self)
    }
}

/// Parameters of [`Connection::prepare`]
///
/// No new variants are added to this enum in the future. That's because
/// a new variant causes breaking changes. New configuration parameters
/// are set via [`StatementBuilder`][] instead.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StmtParam {
    /// The array size used for performing fetches.
    ///
    /// This specifies the number of rows allocated before performing
    /// fetches. The default value is 100. Higher value reduces
    /// the number of network round trips to fetch rows but requires
    /// more memory. The preferable value depends on the query and
    /// the environment.
    ///
    /// If the query returns only onw row, you should use
    /// `StmtParam::FetchArraySize(1)`.
    FetchArraySize(u32),

    /// See [`StatementBuilder::tag`].
    Tag(String),

    /// Reserved for when scrollable cursors are supported.
    Scrollable,
}

/// Statement type returned by [`Statement::statement_type`].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum StatementType {
    /// SELECT statement
    Select,

    /// INSERT statement
    Insert,

    /// UPDATE statement
    Update,

    /// DELETE statement
    Delete,

    /// [MERGE][] statement
    ///
    /// [MERGE]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-5692CCB7-24D9-4C0E-81A7-A22436DC968F
    Merge,

    /// CREATE statement
    Create,

    /// ALTER statement
    Alter,

    /// DROP statement
    Drop,

    /// PL/SQL statement without declare clause
    Begin,

    /// PL/SQL statement with declare clause
    Declare,

    /// COMMIT statement
    Commit,

    /// ROLLBACK statement
    Rollback,

    /// [EXPLAIN PLAN][] statement
    ///
    /// [EXPLAIN PLAN]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-FD540872-4ED3-4936-96A2-362539931BA0
    ExplainPlan,

    /// [CALL][] statement
    ///
    /// [CALL]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-6CD7B9C4-E5DC-4F3C-9B6A-876AD2C63545
    Call,

    /// Unknown statement
    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: *mut dpiStmt,
    pub(crate) column_info: Vec<ColumnInfo>,
    pub(crate) row: Option<Row>,
    shared_buffer_row_index: Rc<AtomicU32>,
    pub(crate) query_params: QueryParams,
    tag: String,
}

impl Stmt {
    pub(crate) fn new(
        conn: Conn,
        handle: *mut dpiStmt,
        query_params: QueryParams,
        tag: String,
    ) -> Stmt {
        Stmt {
            conn,
            handle,
            column_info: Vec::new(),
            row: None,
            shared_buffer_row_index: Rc::new(AtomicU32::new(0)),
            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
    }

    fn close(&mut self) -> Result<()> {
        let tag = to_odpi_str(&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<()> {
        let mut column_names = Vec::with_capacity(num_cols);
        let mut column_values = Vec::with_capacity(num_cols);
        self.column_info = Vec::with_capacity(num_cols);

        for i in 0..num_cols {
            // set column info
            let ci = ColumnInfo::new(self, i)?;
            column_names.push(ci.name.clone());
            self.column_info.push(ci);
            // setup column value
            let mut val = SqlValue::for_column(
                self.conn.clone(),
                self.query_params.clone(),
                self.query_params.fetch_array_size,
            );
            val.buffer_row_index = BufferRowIndex::Shared(self.shared_buffer_row_index.clone());
            let oratype = self.column_info[i].oracle_type();
            let oratype_i64 = OracleType::Int64;
            let oratype = match *oratype {
                // When the column type is number whose prec is less than 18
                // and the scale is zero, define it as int64.
                OracleType::Number(prec, 0) if 0 < prec && prec < DPI_MAX_INT64_PRECISION as u8 => {
                    &oratype_i64
                }
                _ => oratype,
            };
            val.init_handle(oratype)?;
            chkerr!(
                self.ctxt(),
                dpiStmt_define(self.handle, (i + 1) as u32, val.handle)
            );
            column_values.push(val);
        }
        self.row = Some(Row::new(column_names, column_values)?);
        Ok(())
    }

    fn try_next(&self) -> Result<Option<&Row>> {
        let mut found = 0;
        let mut buffer_row_index = 0;
        chkerr!(
            self.ctxt(),
            dpiStmt_fetch(self.handle, &mut found, &mut buffer_row_index)
        );
        Ok(if found != 0 {
            self.shared_buffer_row_index
                .store(buffer_row_index, Ordering::Relaxed);
            // if self.row.is_none(), dpiStmt_fetch() returns non-zero.
            Some(self.row.as_ref().unwrap())
        } else {
            None
        })
    }

    pub fn next(&self) -> Option<Result<&Row>> {
        self.try_next().transpose()
    }

    pub fn row_count(&self) -> Result<u64> {
        let mut count = 0;
        chkerr!(self.ctxt(), dpiStmt_getRowCount(self.handle, &mut count));
        Ok(count)
    }
}

impl Drop for Stmt {
    fn drop(&mut self) {
        let _ = self.close();
        unsafe { dpiStmt_release(self.handle) };
    }
}

/// Statement
#[cfg_attr(feature = "stmt_without_lifetime", remove_stmt_lifetime)]
#[derive(Debug)]
pub struct Statement<'conn> {
    pub(crate) stmt: Stmt,
    statement_type: StatementType,
    is_returning: bool,
    bind_count: usize,
    bind_names: Vec<String>,
    bind_values: Vec<SqlValue>,
    #[cfg(not(feature = "stmt_without_lifetime"))]
    phantom: PhantomData<&'conn ()>,
}

#[cfg_attr(feature = "stmt_without_lifetime", remove_stmt_lifetime)]
impl<'conn> Statement<'conn> {
    pub(crate) fn from_params(
        conn: &'conn Connection,
        sql: &str,
        params: &[StmtParam],
    ) -> Result<Statement<'conn>> {
        let mut builder = conn.statement(sql);
        for param in params {
            match param {
                StmtParam::FetchArraySize(size) => {
                    builder.fetch_array_size(*size);
                }
                StmtParam::Scrollable => {
                    builder.scrollable(true);
                }
                StmtParam::Tag(name) => {
                    builder.tag(name);
                }
            }
        }
        builder.build()
    }

    fn new(builder: &StatementBuilder<'conn, '_>) -> Result<Statement<'conn>> {
        let conn = builder.conn;
        let sql = to_odpi_str(builder.sql);
        let tag = to_odpi_str(&builder.tag);
        let mut handle: *mut dpiStmt = ptr::null_mut();
        chkerr!(
            conn.ctxt(),
            dpiConn_prepareStmt(
                conn.handle(),
                i32::from(builder.scrollable),
                sql.ptr,
                sql.len,
                tag.ptr,
                tag.len,
                &mut handle
            )
        );
        let mut info = MaybeUninit::uninit();
        chkerr!(
            conn.ctxt(),
            dpiStmt_getInfo(handle, info.as_mut_ptr()),
            unsafe {
                dpiStmt_release(handle);
            }
        );
        let info = unsafe { info.assume_init() };
        let mut num = 0;
        chkerr!(
            conn.ctxt(),
            dpiStmt_getBindCount(handle, &mut num),
            unsafe {
                dpiStmt_release(handle);
            }
        );
        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, &mut num, names.as_mut_ptr(), lengths.as_mut_ptr()),
                unsafe {
                    dpiStmt_release(handle);
                }
            );
            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), unsafe {
                dpiStmt_release(handle);
            });
            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,
            #[cfg(not(feature = "stmt_without_lifetime"))]
            phantom: PhantomData,
        })
    }

    /// Closes the statement before the end of lifetime.
    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
    }

    /// Executes the prepared statement and returns a result set containing [`Row`]s.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    pub fn query(&mut self, params: &[&dyn ToSql]) -> Result<ResultSet<Row>> {
        self.exec(params, true, "query")?;
        Ok(ResultSet::<Row>::new(&self.stmt))
    }

    /// Executes the prepared statement using named parameters and returns a result set containing [`Row`]s.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    pub fn query_named(&mut self, params: &[(&str, &dyn ToSql)]) -> Result<ResultSet<Row>> {
        self.exec_named(params, true, "query_named")?;
        Ok(ResultSet::<Row>::new(&self.stmt))
    }

    /// Executes the prepared statement and returns a result set containing [`RowValue`]s.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    pub fn query_as<'a, T>(&'a mut self, params: &[&dyn ToSql]) -> Result<ResultSet<'a, T>>
    where
        T: RowValue,
    {
        self.exec(params, true, "query_as")?;
        Ok(ResultSet::new(&self.stmt))
    }

    /// Executes the prepared statement and returns a result set containing [`RowValue`]s.
    ///
    /// This is the same as [`Statement::query_as()`], but takes ownership of the [`Statement`].
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    pub fn into_result_set<'a, T>(mut self, params: &[&dyn ToSql]) -> Result<ResultSet<'a, T>>
    where
        T: RowValue,
    {
        self.exec(params, true, "into_result_set")?;
        Ok(ResultSet::from_stmt(self.stmt))
    }

    /// Executes the prepared statement using named parameters and returns a result set containing [`RowValue`]s.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    pub fn query_as_named<'a, T>(
        &'a mut self,
        params: &[(&str, &dyn ToSql)],
    ) -> Result<ResultSet<'a, T>>
    where
        T: RowValue,
    {
        self.exec_named(params, true, "query_as_named")?;
        Ok(ResultSet::new(&self.stmt))
    }

    /// Executes the prepared statement using named parameters and returns a result set containing [`RowValue`]s.
    ///
    /// This is the same as [`Statement::query_as_named()`], but takes ownership of the [`Statement`].
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    pub fn into_result_set_named<'a, T>(
        mut self,
        params: &[(&str, &dyn ToSql)],
    ) -> Result<ResultSet<'a, T>>
    where
        T: RowValue,
    {
        self.exec_named(params, true, "into_result_set_named")?;
        Ok(ResultSet::from_stmt(self.stmt))
    }

    /// Gets one row from the prepared statement using positoinal bind parameters.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    pub fn query_row(&mut self, params: &[&dyn ToSql]) -> Result<Row> {
        let mut rows = self.query(params)?;
        rows.next().unwrap_or(Err(Error::NoDataFound))
    }

    /// Gets one row from the prepared statement using named bind parameters.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    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::NoDataFound))
    }

    /// Gets one row from the prepared statement as specified type using positoinal bind parameters.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    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::NoDataFound))
    }

    /// Gets one row from the prepared statement as specified type using named bind parameters.
    ///
    /// See [Query Methods][].
    ///
    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
    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::NoDataFound))
    }

    /// Binds values by position and executes the statement.
    /// It will retunrs `Err` when the statemnet is a select statement.
    ///
    /// See also [`Connection::execute`].
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use oracle::*;
    /// let conn = Connection::connect("scott", "tiger", "")?;
    ///
    /// // execute a statement without bind parameters
    /// let mut stmt = conn
    ///     .statement("insert into emp(empno, ename) values (113, 'John')")
    ///     .build()?;
    /// stmt.execute(&[])?;
    ///
    /// // execute a statement with binding parameters by position
    /// let mut stmt = conn
    ///     .statement("insert into emp(empno, ename) values (:1, :2)")
    ///     .build()?;
    /// stmt.execute(&[&114, &"Smith"])?;
    /// stmt.execute(&[&115, &"Paul"])?;  // execute with other values.
    ///
    /// # Ok::<(), Error>(())
    /// ```
    pub fn execute(&mut self, params: &[&dyn ToSql]) -> Result<()> {
        self.exec(params, false, "execute")
    }

    /// Binds values by name and executes the statement.
    /// It will retunrs `Err` when the statemnet is a select statement.
    ///
    /// See also [`Connection::execute_named`].
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use oracle::*;
    /// let conn = Connection::connect("scott", "tiger", "")?;
    ///
    /// // execute a statement with binding parameters by name
    /// let mut stmt = conn
    ///     .statement("insert into emp(empno, ename) values (:id, :name)")
    ///     .build()?;
    /// stmt.execute_named(&[("id", &114),
    ///                      ("name", &"Smith")])?;
    /// stmt.execute_named(&[("id", &115),
    ///                      ("name", &"Paul")])?; // execute with other values.
    /// # Ok::<(), Error>(())
    /// ```
    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::InvalidOperation(format!(
                    "Could not use the `{}` method for non-select statements",
                    method_name
                )))
            }
        } else if self.statement_type != StatementType::Select {
            Ok(())
        } else {
            Err(Error::InvalidOperation(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.row.is_none() {
            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(())
    }

    /// Returns the number of bind variables in the statement.
    ///
    /// In SQL statements this is the total number of bind variables whereas in
    /// PL/SQL statements this is the count of the **unique** bind variables.
    ///
    /// ```no_run
    /// # use oracle::*;
    /// let conn = Connection::connect("scott", "tiger", "")?;
    ///
    /// // SQL statements
    /// let stmt = conn.statement("select :val1, :val2, :val1 from dual").build()?;
    /// assert_eq!(stmt.bind_count(), 3); // val1, val2 and val1
    ///
    /// // PL/SQL statements
    /// let stmt = conn.statement("begin :val1 := :val1 || :val2; end;").build()?;
    /// assert_eq!(stmt.bind_count(), 2); // val1(twice) and val2
    /// # Ok::<(), Error>(())
    /// ```
    pub fn bind_count(&self) -> usize {
        self.bind_count
    }

    /// Returns the names of the unique bind variables in the statement.
    ///
    /// The bind variable names in statements are converted to upper-case.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use oracle::*;
    /// let conn = Connection::connect("scott", "tiger", "")?;
    ///
    /// let stmt = conn.statement("BEGIN :val1 := :val2 || :val1 || :aàáâãäå; END;").build()?;
    /// assert_eq!(stmt.bind_count(), 3);
    /// let bind_names = stmt.bind_names();
    /// assert_eq!(bind_names.len(), 3);
    /// assert_eq!(bind_names[0], "VAL1");
    /// assert_eq!(bind_names[1], "VAL2");
    /// assert_eq!(bind_names[2], "AÀÁÂÃÄÅ");
    /// # Ok::<(), Error>(())
    /// ```
    pub fn bind_names(&self) -> Vec<&str> {
        self.bind_names.iter().map(|name| name.as_str()).collect()
    }

    /// Set a bind value in the statement.
    ///
    /// The position starts from one when the bind index type is `usize`.
    /// The variable name is compared case-insensitively when the bind index
    /// type is `&str`.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use oracle::*; use oracle::sql_type::*;
    /// let conn = Connection::connect("scott", "tiger", "")?;
    /// let mut stmt = conn.statement("begin :outval := upper(:inval); end;").build()?;
    ///
    /// // Sets NULL whose data type is VARCHAR2(60) to the first bind value.
    /// stmt.bind(1, &OracleType::Varchar2(60))?;
    ///
    /// // Sets "to be upper-case" to the second by its name.
    /// stmt.bind("inval", &"to be upper-case")?;
    ///
    /// stmt.execute(&[])?;
    /// let outval: String = stmt.bind_value(1)?;
    /// assert_eq!(outval, "TO BE UPPER-CASE");
    /// # Ok::<(), Error>(())
    /// ```
    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)
    }

    /// Gets a bind value in the statement.
    ///
    /// The position starts from one when the bind index type is `usize`.
    /// The variable name is compared case-insensitively when the bind index
    /// type is `&str`.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use oracle::*; use oracle::sql_type::*;
    /// let conn = Connection::connect("scott", "tiger", "")?;
    ///
    /// // Prepares "begin :outval := upper(:inval); end;",
    /// // sets NULL whose data type is VARCHAR2(60) to the first bind variable,
    /// // sets "to be upper-case" to the second and then executes it.
    /// let mut stmt = conn.statement("begin :outval := upper(:inval); end;").build()?;
    /// stmt.execute(&[&OracleType::Varchar2(60),
    ///              &"to be upper-case"])?;
    ///
    /// // Get the first bind value by position.
    /// let outval: String = stmt.bind_value(1)?;
    /// assert_eq!(outval, "TO BE UPPER-CASE");
    ///
    /// // Get the first bind value by name.
    /// let outval: String = stmt.bind_value("outval")?;
    /// assert_eq!(outval, "TO BE UPPER-CASE");
    /// # Ok::<(), Error>(())
    /// ```
    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()
    }

    /// Gets values returned by RETURNING INTO clause.
    ///
    /// When the `bindidx` ponints to a bind variable out of RETURNING INTO clause,
    /// the behavior is undefined.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use oracle::*; use oracle::sql_type::*;
    /// let conn = Connection::connect("scott", "tiger", "")?;
    ///
    /// // create a table using identity column (Oracle 12c feature).
    /// conn.execute("create table people (id number generated as identity, name varchar2(30))", &[])?;
    ///
    /// // insert one person and return the generated id into :id.
    /// let stmt = conn.execute("insert into people(name) values ('Asimov') returning id into :id", &[&None::<i32>])?;
    /// let inserted_id: i32 = stmt.returned_values("id")?[0];
    /// println!("Asimov's ID is {}", inserted_id);
    ///
    /// // insert another person and return the generated id into :id.
    /// let stmt = conn.execute("insert into people(name) values ('Clark') returning id into :id", &[&None::<i32>])?;
    /// let inserted_id: i32 = stmt.returned_values("id")?[0];
    /// println!("Clark's ID is {}", inserted_id);
    ///
    /// // delete all people and return deleted names into :name.
    /// let stmt = conn.execute("delete from people returning name into :name", &[&OracleType::Varchar2(30)])?;
    /// let deleted_names: Vec<String> = stmt.returned_values("name")?;
    /// for name in deleted_names {
    ///     println!("{} is deleted.", name);
    /// }
    ///
    /// // cleanup
    /// conn.execute("drop table people purge", &[])?;
    /// # Ok::<(), Error>(())
    /// ```
    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)?].unsafely_clone();
        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)
    }

    /// Returns the number of rows fetched when the SQL statement is a query.
    /// Otherwise, the number of rows affected.
    ///
    /// # Examples
    ///
    /// ```
    /// # use oracle::Error;
    /// # use oracle::test_util::{self, check_version, VER12_1};
    /// # let conn = test_util::connect()?;
    /// // number of affected rows
    /// let stmt = conn.execute("update TestStrings set StringCol = StringCol where IntCol >= :1", &[&6])?;
    /// assert_eq!(stmt.row_count()?, 5);
    ///
    /// // number of fetched rows
    /// let mut stmt = conn
    ///     .statement("select * from TestStrings where IntCol >= :1")
    ///     .build()?;
    /// assert_eq!(stmt.row_count()?, 0); // before fetch
    /// let mut nrows = 0;
    /// for _ in stmt.query(&[&6])? {
    ///   nrows += 1;
    /// }
    /// assert_eq!(stmt.row_count()?, nrows); // after fetch
    ///
    /// // fetch again using same stmt with a different bind value.
    /// let mut nrows = 0;
    /// for _ in stmt.query(&[&4])? {
    ///   nrows += 1;
    /// }
    /// assert_eq!(stmt.row_count()?, nrows); // after fetch
    /// # Ok::<(), Error>(())
    /// ```
    pub fn row_count(&self) -> Result<u64> {
        self.stmt.row_count()
    }

    /// Returns the next implicit result returned by [`dbms_sql.return_result()`]
    /// in a PL/SQL block or a stored procedure.
    ///
    /// This feature is available when both the client and server are 12.1 or higher.
    ///
    /// [`dbms_sql.return_result()`]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-87562BF3-682C-48A7-B0C1-61075F19382A
    ///
    /// # Examples
    ///
    /// ```
    /// # use oracle::Error;
    /// # use oracle::test_util::{self, check_version, VER12_1};
    /// # let conn = test_util::connect()?;
    /// # if !check_version(&conn, &VER12_1, &VER12_1)? {
    /// #     return Ok(()); // skip this test
    /// # }
    ///
    /// let sql = r#"
    /// declare
    ///   cursor1 SYS_REFCURSOR;
    ///   cursor2 SYS_REFCURSOR;
    /// begin
    ///   open cursor1 for select StringCol from TestStrings where IntCol = :1;
    ///   -- return the first result set
    ///   dbms_sql.return_result(cursor1);
    ///
    ///   open cursor2 for select StringCol from TestStrings where IntCol = :2;
    ///   -- return the second result set
    ///   dbms_sql.return_result(cursor2);
    /// end;
    /// "#;
    ///
    /// let mut stmt = conn.statement(sql).build()?;
    /// stmt.execute(&[&1, &2])?;
    ///
    /// // Get the first result set.
    /// let mut opt_cursor = stmt.implicit_result()?;
    /// assert!(opt_cursor.is_some());
    /// let mut cursor = opt_cursor.unwrap();
    /// assert_eq!(cursor.query_row_as::<String>()?, "String 1");
    ///
    /// // Get the second result set.
    /// let mut opt_cursor = stmt.implicit_result()?;
    /// assert!(opt_cursor.is_some());
    /// let mut cursor = opt_cursor.unwrap();
    /// assert_eq!(cursor.query_row_as::<String>()?, "String 2");
    ///
    /// // No more result sets
    /// let mut opt_cursor = stmt.implicit_result()?;
    /// assert!(opt_cursor.is_none());
    /// # Ok::<(), Error>(())
    /// ```
    pub fn implicit_result(&self) -> Result<Option<RefCursor>> {
        let mut handle = ptr::null_mut();
        chkerr!(
            self.ctxt(),
            dpiStmt_getImplicitResult(self.handle(), &mut handle)
        );
        if handle.is_null() {
            Ok(None)
        } else {
            let cursor = RefCursor::from_raw(
                self.stmt.conn.clone(),
                handle,
                self.stmt.query_params.clone(),
            )?;
            unsafe {
                dpiStmt_release(handle);
            }
            Ok(Some(cursor))
        }
    }

    /// Returns statement type
    pub fn statement_type(&self) -> StatementType {
        self.statement_type
    }

    /// Returns true when the SQL statement is a query.
    pub fn is_query(&self) -> bool {
        self.statement_type == StatementType::Select
    }

    /// Returns true when the SQL statement is a PL/SQL block.
    pub fn is_plsql(&self) -> bool {
        matches!(
            self.statement_type,
            StatementType::Begin | StatementType::Declare | StatementType::Call
        )
    }

    /// Returns true when the SQL statement is DDL (data definition language).
    pub fn is_ddl(&self) -> bool {
        matches!(
            self.statement_type,
            StatementType::Create | StatementType::Drop | StatementType::Alter
        )
    }

    /// Returns true when the SQL statement is DML (data manipulation language).
    pub fn is_dml(&self) -> bool {
        matches!(
            self.statement_type,
            StatementType::Insert
                | StatementType::Update
                | StatementType::Delete
                | StatementType::Merge
        )
    }

    /// Returns true when the SQL statement has a `RETURNING INTO` clause.
    pub fn is_returning(&self) -> bool {
        self.is_returning
    }

    /// Returns the rowid of the last row that was affected by the statement.
    ///
    /// ```
    /// # use oracle::Error;
    /// # use oracle::test_util;
    /// # let conn = test_util::connect()?;
    /// let mut stmt = conn
    ///     .statement("insert into TestDates values(100, sysdate, null)")
    ///     .build()?;
    /// stmt.execute(&[])?;
    /// // get the rowid inserted by stmt
    /// let rowid1 = stmt.last_row_id()?;
    /// // get the rowid from database
    /// let rowid2 = conn.query_row_as::<String>("select rowid from TestDates where IntCol = 100", &[])?;
    /// assert_eq!(rowid1, Some(rowid2));
    /// # conn.rollback()?;
    /// # Ok::<(), Error>(())
    /// ```
    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)))
        }
    }

    /// Gets an OCI handle attribute corresponding to the specified type parameter
    /// See the [`oci_attr` module][crate::oci_attr] for details.
    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) }
    }

    /// Sets an OCI handle attribute corresponding to the specified type parameter
    /// See the [`oci_attr` module][crate::oci_attr] for details.
    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) }
    }
}

/// Column information in a select statement
///
/// # Examples
///
/// Print column information of `emp` table.
///
/// ```no_run
/// # use oracle::*;
/// let conn = Connection::connect("scott", "tiger", "")?;
/// let mut stmt = conn.statement("select * from emp").build()?;
/// let rows = stmt.query(&[])?;
/// println!(" {:-30} {:-8} {}", "Name", "Null?", "Type");
/// println!(" {:-30} {:-8} {}", "------------------------------", "--------", "----------------------------");
/// for info in rows.column_info() {
///    println!("{:-30} {:-8} {}",
///             info.name(),
///             if info.nullable() {""} else {"NOT NULL"},
///             info.oracle_type());
/// }
/// # Ok::<(), Error>(())
/// ```
///
/// The output is:
///
/// ```text
///  Name                           Null?    Type
///  ------------------------------ -------- ----------------------------
///  EMPNO                          NOT NULL NUMBER(4)
///  ENAME                                   VARCHAR2(10)
///  JOB                                     VARCHAR2(9)
///  MGR                                     NUMBER(4)
///  HIREDATE                                DATE
///  SAL                                     NUMBER(7,2)
///  COMM                                    NUMBER(7,2)
///  DEPTNO                                  NUMBER(2)
/// ```
#[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,
        })
    }

    /// Gets column name
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Gets Oracle type
    pub fn oracle_type(&self) -> &OracleType {
        &self.oracle_type
    }

    /// Gets whether the column may be NULL.
    /// False when the column is defined as `NOT NULL`.
    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)
        }
    }
}

/// A trait implemented by types that can index into bind values of a statement.
///
/// This trait is sealed and cannot be implemented for types outside of the `oracle` crate.
pub trait BindIndex: private::Sealed {
    /// Returns the index of the bind value specified by `self`.
    #[doc(hidden)]
    fn idx(&self, stmt: &Statement) -> Result<usize>;
    /// Binds the specified value by using a private method.
    #[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::InvalidBindIndex(*self))
        }
    }

    unsafe fn bind(&self, stmt_handle: *mut dpiStmt, var_handle: *mut dpiVar) -> i32 {
        dpiStmt_bindByPos(stmt_handle, *self as u32, var_handle)
    }
}

impl<'a> BindIndex for &'a 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::InvalidBindName((*self).to_string()))
    }

    unsafe fn bind(&self, stmt_handle: *mut dpiStmt, var_handle: *mut dpiVar) -> i32 {
        let s = to_odpi_str(self);
        dpiStmt_bindByName(stmt_handle, s.ptr, s.len, var_handle)
    }
}

/// A trait implemented by types that can index into columns of a row.
///
/// This trait is sealed and cannot be implemented for types outside of the `oracle` crate.
pub trait ColumnIndex: private::Sealed {
    /// Returns the index of the column specified by `self`.
    #[doc(hidden)]
    fn idx(&self, column_names: &[String]) -> Result<usize>;
}

impl ColumnIndex for usize {
    fn idx(&self, column_names: &[String]) -> Result<usize> {
        let ncols = column_names.len();
        if *self < ncols {
            Ok(*self)
        } else {
            Err(Error::InvalidColumnIndex(*self))
        }
    }
}

impl<'a> ColumnIndex for &'a str {
    fn idx(&self, column_names: &[String]) -> Result<usize> {
        for (idx, colname) in column_names.iter().enumerate() {
            if colname.as_str().eq_ignore_ascii_case(self) {
                return Ok(idx);
            }
        }
        Err(Error::InvalidColumnName((*self).to_string()))
    }
}

#[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");
        // xmltype is reported as long. (https://github.com/oracle/odpi/commit/d51e9c496381427dd09f7fb0cbaab3e152667026)
        assert_eq!(colinfo[1].oracle_type(), &OracleType::Long);
        assert_eq!(colinfo.len(), 2);

        Ok(())
    }
}

#[cfg_attr(feature = "stmt_without_lifetime", doc = "```")]
#[cfg_attr(not(feature = "stmt_without_lifetime"), doc = "```compile_fail")]
/// # use oracle::Statement;
/// struct Statements {
///   stmts: Vec<Statement>,
/// }
/// ```
#[cfg(doctest)]
struct TestStmtWithoutLifetime;