Documentation
// Rust-oracle - Rust binding for Oracle database
//
// URL: https://github.com/kubo/rust-oracle
//
//-----------------------------------------------------------------------------
// Copyright (c) 2017-2023 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)
//-----------------------------------------------------------------------------

#![doc = include_str!("../README.md")]

use std::os::raw::c_char;
use std::ptr;
use std::result;
use std::slice;

#[cfg(feature = "aq_unstable")]
pub mod aq;
mod batch;
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(improper_ctypes)]
mod binding;
pub mod conn;
mod connection;
mod context;
mod error;
pub mod io;
pub mod oci_attr;
pub mod pool;
#[cfg(doctest)]
mod procmacro;
mod row;
pub mod sql_type;
mod sql_value;
mod statement;
mod util;
mod version;

pub use crate::batch::Batch;
pub use crate::batch::BatchBindIndex;
pub use crate::batch::BatchBuilder;
pub use crate::connection::ConnStatus;
pub use crate::connection::Connection;
pub use crate::connection::Connector;
pub use crate::connection::Privilege;
pub use crate::connection::ShutdownMode;
pub use crate::connection::StartupMode;
use crate::context::Context;
pub use crate::error::DbError;
pub use crate::error::Error;
pub use crate::error::ParseOracleTypeError;
pub use crate::row::ResultSet;
pub use crate::row::Row;
pub use crate::row::RowValue;
pub use crate::sql_value::SqlValue;
pub use crate::statement::BindIndex;
pub use crate::statement::ColumnIndex;
pub use crate::statement::ColumnInfo;
pub use crate::statement::Statement;
pub use crate::statement::StatementBuilder;
pub use crate::statement::StatementType;
pub use crate::statement::StmtParam;
pub use crate::version::Version;
pub use oracle_procmacro::RowValue;

use crate::binding::*;

pub type Result<T> = result::Result<T, Error>;

macro_rules! define_dpi_data_with_refcount {
    ($name:ident) => {
        paste::item! {
            struct [<Dpi $name>] {
                raw: *mut [<dpi $name>],
            }

            impl [<Dpi $name>] {
                fn new(raw: *mut [<dpi $name>]) -> [<Dpi $name>] {
                    [<Dpi $name>] { raw }
                }

                #[allow(dead_code)]
                fn with_add_ref(raw: *mut [<dpi $name>]) -> [<Dpi $name>] {
                    unsafe { [<dpi $name _addRef>](raw) };
                    [<Dpi $name>] { raw }
                }

                pub(crate) fn raw(&self) -> *mut [<dpi $name>] {
                    self.raw
                }
            }

            impl Clone for [<Dpi $name>] {
                fn clone(&self) -> [<Dpi $name>] {
                    unsafe { [<dpi $name _addRef>](self.raw()) };
                    [<Dpi $name>]::new(self.raw())
                }
            }

            impl Drop for [<Dpi $name>] {
                fn drop(&mut self) {
                    unsafe { [<dpi $name _release>](self.raw()) };
                }
            }

            unsafe impl Send for [<Dpi $name>] {}
            unsafe impl Sync for [<Dpi $name>] {}
        }
    };
}

// define DpiConn wrapping *mut dpiConn.
define_dpi_data_with_refcount!(Conn);

// define DpiMsgProps wrapping *mut dpiMsgProps.
define_dpi_data_with_refcount!(MsgProps);

// define DpiObjectType wrapping *mut dpiObjectType.
define_dpi_data_with_refcount!(ObjectType);

// define DpiPool wrapping *mut dpiPool.
define_dpi_data_with_refcount!(Pool);

// define DpiObjectAttr wrapping *mut dpiObjectAttr.
define_dpi_data_with_refcount!(ObjectAttr);

// define DpiQueue wrapping *mut dpiQueue.
define_dpi_data_with_refcount!(Queue);

trait AssertSend: Send {}
trait AssertSync: Sync {}

//
// Utility struct to convert Rust strings from/to ODPI-C strings
//

struct OdpiStr {
    pub ptr: *const c_char,
    pub len: u32,
}

fn new_odpi_str() -> OdpiStr {
    OdpiStr {
        ptr: ptr::null(),
        len: 0,
    }
}

fn to_odpi_str(s: &str) -> OdpiStr {
    if s.is_empty() {
        OdpiStr {
            ptr: ptr::null(),
            len: 0,
        }
    } else {
        OdpiStr {
            ptr: s.as_ptr() as *const c_char,
            len: s.len() as u32,
        }
    }
}

impl OdpiStr {
    #[allow(clippy::inherent_to_string)]
    pub fn to_string(&self) -> String {
        to_rust_str(self.ptr, self.len)
    }

    #[cfg(feature = "aq_unstable")]
    pub fn to_vec(&self) -> Vec<u8> {
        if self.ptr.is_null() {
            Vec::new()
        } else {
            let ptr = self.ptr as *mut u8;
            let len = self.len as usize;
            unsafe { Vec::from_raw_parts(ptr, len, len) }
        }
    }
}

fn to_rust_str(ptr: *const c_char, len: u32) -> String {
    if ptr.is_null() {
        "".to_string()
    } else {
        let s = unsafe { slice::from_raw_parts(ptr as *mut u8, len as usize) };
        String::from_utf8_lossy(s).into_owned()
    }
}

fn to_rust_slice<'a>(ptr: *const c_char, len: u32) -> &'a [u8] {
    if ptr.is_null() {
        &[]
    } else {
        unsafe { slice::from_raw_parts(ptr as *mut u8, len as usize) }
    }
}

mod private {
    use std::os::raw::c_void;

    pub trait Sealed {}

    impl Sealed for u8 {}
    impl Sealed for u16 {}
    impl Sealed for u32 {}
    impl Sealed for u64 {}
    impl Sealed for usize {}
    impl Sealed for bool {}
    impl Sealed for str {}
    impl Sealed for [u8] {}
    impl Sealed for *mut c_void {}
    impl<'a> Sealed for &'a str {}
}

#[allow(dead_code)]
#[doc(hidden)]
// #[cfg(doctest)] isn't usable here. See: https://github.com/rust-lang/rust/issues/67295
pub mod test_util {
    use super::*;
    use std::env;

    pub const VER11_2: Version = Version::new(11, 2, 0, 0, 0);
    pub const VER12_1: Version = Version::new(12, 1, 0, 0, 0);
    pub const VER18: Version = Version::new(18, 0, 0, 0, 0);

    fn env_var_or(env_name: &str, default: &str) -> String {
        match env::var_os(env_name) {
            Some(env_var) => env_var.into_string().unwrap(),
            None => String::from(default),
        }
    }

    pub fn main_user() -> String {
        env_var_or("ODPIC_TEST_MAIN_USER", "odpic")
    }

    pub fn main_password() -> String {
        env_var_or("ODPIC_TEST_MAIN_PASSWORD", "welcome")
    }

    pub fn edition_user() -> String {
        env_var_or("ODPIC_TEST_EDITION_USER", "odpic_edition")
    }

    pub fn edition_password() -> String {
        env_var_or("ODPIC_TEST_EDITION_PASSWORD", "welcome")
    }

    pub fn connect_string() -> String {
        env_var_or("ODPIC_TEST_CONNECT_STRING", "localhost/orclpdb")
    }

    pub fn connect() -> Result<Connection> {
        Connection::connect(main_user(), main_password(), connect_string())
    }

    pub fn check_version(
        conn: &Connection,
        client_ver: &Version,
        server_ver: &Version,
    ) -> Result<bool> {
        Ok(&Version::client()? >= client_ver && &conn.server_version()?.0 >= server_ver)
    }
}