oracle/
context.rs

1// Rust-oracle - Rust binding for Oracle database
2//
3// URL: https://github.com/kubo/rust-oracle
4//
5//-----------------------------------------------------------------------------
6// Copyright (c) 2017-2023 Kubo Takehiro <kubo@jiubao.org>. All rights reserved.
7// This program is free software: you can modify it and/or redistribute it
8// under the terms of:
9//
10// (i)  the Universal Permissive License v 1.0 or at your option, any
11//      later version (http://oss.oracle.com/licenses/upl); and/or
12//
13// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
14//-----------------------------------------------------------------------------
15
16#[cfg(doc)]
17use crate::pool::PoolBuilder;
18use crate::util::{os_string_into_ansi_c_string, string_into_c_string};
19#[cfg(doc)]
20use crate::Connector;
21use crate::DbError;
22use crate::Error;
23use crate::Result;
24use odpic_sys::*;
25use once_cell::sync::OnceCell;
26use std::ffi::{CString, OsString};
27use std::mem::{self, MaybeUninit};
28use std::os::raw::c_char;
29use std::ptr;
30use std::sync::{Arc, Mutex};
31
32/// Parameters for explicit Oracle client library initialization
33///
34/// Note:
35/// 1. Any method that invokes C functions in the Oracle client library will implicitly initialize it.
36/// 2. Regardless of whether it is initialized explicitly or implicitly, it is only once per process.
37///
38/// # Examples
39///
40/// Initialize explicitly twice
41///
42/// ```
43/// # use oracle::*;
44/// // init() returns Ok(true) on the first call.
45/// assert_eq!(InitParams::new().init()?, true);
46///
47/// // It returns Ok(false) when Oracle client library has initialized already.
48/// assert_eq!(InitParams::new().init()?, false);
49/// # Ok::<(), Error>(())
50/// ```
51///
52/// Initialize implicitly then explicitly
53///
54/// ```
55/// # use oracle::*;
56/// // Oracle client library isn't initialzied at first.
57/// assert_eq!(InitParams::is_initialized(), false);
58///
59/// // It is initialized by any method that invokes C functions in it.
60/// Connection::connect("dummy", "dummy", "");
61///
62/// // Parameters have no effect on the already initialized one.
63/// assert_eq!(
64///     InitParams::new()
65///         .oracle_client_lib_dir("/another/oracle/client/location")?
66///         .init()?,
67///     false
68/// );
69/// # Ok::<(), Error>(())
70/// ```
71#[derive(Clone, Debug)]
72pub struct InitParams {
73    default_driver_name: Option<CString>,
74    load_error_url: Option<CString>,
75    oracle_client_lib_dir: Option<CString>,
76    oracle_client_config_dir: Option<CString>,
77    soda_use_json_desc: bool,
78    use_json_id: bool,
79}
80
81impl InitParams {
82    /// Creates a new initialization parameter
83    pub fn new() -> InitParams {
84        InitParams {
85            default_driver_name: None,
86            load_error_url: None,
87            oracle_client_lib_dir: None,
88            oracle_client_config_dir: None,
89            soda_use_json_desc: false,
90            use_json_id: false,
91        }
92    }
93
94    /// Sets the default driver name to use when creating pools or standalone connections.
95    ///
96    /// The standard is to set this value to `"<name> : <version>"`, where `<name>`
97    /// is the name of the driver and `<version>` is its version. There should be a
98    /// single space character before and after the colon.
99    ///
100    /// This value is shown in database views that give information about
101    /// connections. For example, it is in the `CLIENT_DRIVER` column
102    /// of [`V$SESSION_CONNECT_INFO`].
103    ///
104    /// If this member isn't set, then the default value is `"rust-oracle : <version>"`,
105    /// where `<version>` is the oracle crate version.
106    ///
107    /// This value is propagated to the default value of [`Connector::driver_name`]
108    /// and [`PoolBuilder::driver_name`].
109    ///
110    /// # Errors
111    ///
112    /// If `name` contains null characters, an error will be returned.
113    ///
114    /// [`V$SESSION_CONNECT_INFO`]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-9F0DCAEA-A67E-4183-89E7-B1555DC591CE
115    pub fn default_driver_name<T>(&mut self, name: T) -> Result<&mut InitParams>
116    where
117        T: Into<String>,
118    {
119        self.default_driver_name = Some(string_into_c_string(name.into(), "default_driver_name")?);
120        Ok(self)
121    }
122
123    /// Sets the URL that should be provided in the error message returned
124    /// when the Oracle Client library cannot be loaded.
125    ///
126    /// This URL should direct the user to the installation instructions for
127    /// the application or driver using ODPI-C. If this value isn't set then
128    /// the default ODPI-C URL is provided in the error message instead.
129    ///
130    /// # Errors
131    ///
132    /// If `url` contains null characters, an error will be returned.
133    pub fn load_error_url<T>(&mut self, url: T) -> Result<&mut InitParams>
134    where
135        T: Into<String>,
136    {
137        self.load_error_url = Some(string_into_c_string(url.into(), "load_error_url")?);
138        Ok(self)
139    }
140
141    /// Sets the location from which to load the Oracle Client library.
142    ///
143    /// If this value is set it is the only location that is searched;
144    /// otherwise, if this value isn't set the Oracle Client library is
145    /// searched for in the usual fashion as noted in [Oracle Client Library Loading][clientlibloading].
146    /// Also see that section for limitations on using this.
147    ///
148    /// # Errors
149    ///
150    /// If `dir` contains null characters, an error will be returned.
151    ///
152    /// On windows, `dir` must consist with characters convertible to [ANSI code page],
153    /// which is, for example, [CP1252] in English, [CP932] in Japanese.
154    /// Otherwise, an error will be returned.
155    ///
156    /// [clientlibloading]: https://odpi-c.readthedocs.io/en/latest/user_guide/installation.html#oracle-client-library-loading
157    /// [ANSI code page]: https://en.wikipedia.org/wiki/Windows_code_page#ANSI_code_page
158    /// [CP1252]: https://en.wikipedia.org/wiki/Windows-1252
159    /// [CP932]: https://en.wikipedia.org/wiki/Code_page_932_(Microsoft_Windows)
160    pub fn oracle_client_lib_dir<T>(&mut self, dir: T) -> Result<&mut InitParams>
161    where
162        T: Into<OsString>,
163    {
164        self.oracle_client_lib_dir = Some(os_string_into_ansi_c_string(
165            dir.into(),
166            "oracle_client_lib_dir",
167        )?);
168        Ok(self)
169    }
170
171    /// Sets the location the Oracle client library will search for
172    /// configuration files.
173    ///
174    /// This is equivalent to setting the environment variable `TNS_ADMIN`.
175    /// If this value is set, it overrides any value set by the environment
176    /// variable `TNS_ADMIN`.
177    ///
178    /// # Errors
179    ///
180    /// If `dir` contains null characters, an error will be returned.
181    ///
182    /// On windows, `dir` must consist with characters convertible to [ANSI code page],
183    /// which is, for example, [CP1252] in English, [CP932] in Japanese.
184    /// Otherwise, an error will be returned.
185    ///
186    /// [ANSI code page]: https://en.wikipedia.org/wiki/Windows_code_page#ANSI_code_page
187    /// [CP1252]: https://en.wikipedia.org/wiki/Windows-1252
188    /// [CP932]: https://en.wikipedia.org/wiki/Code_page_932_(Microsoft_Windows)
189    pub fn oracle_client_config_dir<T>(&mut self, dir: T) -> Result<&mut InitParams>
190    where
191        T: Into<OsString>,
192    {
193        self.oracle_client_config_dir = Some(os_string_into_ansi_c_string(
194            dir.into(),
195            "oracle_client_config_dir",
196        )?);
197        Ok(self)
198    }
199
200    // SODA has not been supported yet.
201    #[doc(hidden)]
202    pub fn soda_use_json_desc(&mut self, value: bool) -> &mut InitParams {
203        self.soda_use_json_desc = value;
204        self
205    }
206
207    // JSON has not been supported yet.
208    #[doc(hidden)]
209    pub fn use_json_id(&mut self, value: bool) -> &mut InitParams {
210        self.use_json_id = value;
211        self
212    }
213
214    /// Initializes Oracle client library.
215    ///
216    /// It returns `Ok(true)` when Oracle client library hasn't been initialized
217    /// yet and it is initialized successfully.
218    ///
219    /// It returns `Ok(false)` when Oracle client library has been initialized
220    /// already. Parameter values in `self` affect nothing.
221    ///
222    /// Otherwise, it retruns an error.
223    pub fn init(&self) -> Result<bool> {
224        let mut initialized_here = false;
225        GLOBAL_CONTEXT.get_or_try_init(|| {
226            let mut params = unsafe { mem::zeroed::<dpiContextCreateParams>() };
227            if let Some(ref name) = self.default_driver_name {
228                params.defaultDriverName = name.as_ptr();
229            }
230            if let Some(ref url) = self.load_error_url {
231                params.loadErrorUrl = url.as_ptr();
232            }
233            if let Some(ref dir) = self.oracle_client_lib_dir {
234                params.oracleClientLibDir = dir.as_ptr()
235            }
236            if let Some(ref dir) = self.oracle_client_config_dir {
237                params.oracleClientConfigDir = dir.as_ptr()
238            }
239            params.sodaUseJsonDesc = self.soda_use_json_desc.into();
240            params.useJsonId = self.use_json_id.into();
241            let result = Context::from_params(&mut params);
242            initialized_here = true;
243            result
244        })?;
245        Ok(initialized_here)
246    }
247
248    /// Returns `true` if Oracle client library has initialized already.
249    ///
250    /// # Examples
251    ///
252    /// ```
253    /// # use oracle::*;
254    ///
255    /// // `false` at first
256    /// assert_eq!(InitParams::is_initialized(), false);
257    ///
258    /// // Use any method that invokes C functions in the Oracle client library.
259    /// Connection::connect("dummy", "dummy", "");
260    ///
261    /// // `true` here
262    /// assert_eq!(InitParams::is_initialized(), true);
263    /// # Ok::<(), Error>(())
264    /// ```
265    pub fn is_initialized() -> bool {
266        GLOBAL_CONTEXT.get().is_some()
267    }
268}
269
270impl Default for InitParams {
271    fn default() -> Self {
272        Self::new()
273    }
274}
275
276//
277// Context
278//
279
280// Context is created for each connection.
281// On the other hand, the context member (*mut dpiContext) is created only once in the process.
282//
283// It is used to share information between Connection and structs created from the Connection.
284#[derive(Clone)]
285pub(crate) struct Context {
286    pub context: *mut dpiContext,
287    last_warning: Option<Arc<Mutex<Option<DbError>>>>,
288}
289
290unsafe impl Sync for Context {}
291unsafe impl Send for Context {}
292
293static GLOBAL_CONTEXT: OnceCell<Context> = OnceCell::new();
294
295impl Context {
296    // Use this only inside of GLOBAL_CONTEXT.get_or_try_init().
297    fn from_params(params: &mut dpiContextCreateParams) -> Result<Context> {
298        if params.defaultDriverName.is_null() {
299            let driver_name: &'static str =
300                concat!("rust-oracle : ", env!("CARGO_PKG_VERSION"), "\0");
301            params.defaultDriverName = driver_name.as_ptr() as *const c_char;
302        }
303        let mut ctxt = ptr::null_mut();
304        let mut err = MaybeUninit::uninit();
305        if unsafe {
306            dpiContext_createWithParams(
307                DPI_MAJOR_VERSION,
308                DPI_MINOR_VERSION,
309                params,
310                &mut ctxt,
311                err.as_mut_ptr(),
312            )
313        } == DPI_SUCCESS as i32
314        {
315            Ok(Context {
316                context: ctxt,
317                last_warning: None,
318            })
319        } else {
320            Err(Error::from_dpi_error(&unsafe { err.assume_init() }))
321        }
322    }
323
324    pub fn new0() -> Result<Context> {
325        Ok(GLOBAL_CONTEXT
326            .get_or_try_init(|| {
327                let mut params = unsafe { mem::zeroed() };
328                Context::from_params(&mut params)
329            })?
330            .clone())
331    }
332
333    pub fn new() -> Result<Context> {
334        let ctxt = Context::new0()?;
335        Ok(Context {
336            last_warning: Some(Arc::new(Mutex::new(None))),
337            ..ctxt
338        })
339    }
340
341    // called by Connection::last_warning
342    pub fn last_warning(&self) -> Option<DbError> {
343        self.last_warning
344            .as_ref()
345            .and_then(|mutex| mutex.lock().unwrap().as_ref().cloned())
346    }
347
348    // called by Connection, Statement, Batch and Pool to set a warning
349    // referred by `Connection::last_warning` later.
350    pub fn set_warning(&self) {
351        if let Some(ref mutex) = self.last_warning {
352            *mutex.lock().unwrap() = DbError::to_warning(self);
353        }
354    }
355
356    pub fn common_create_params(&self) -> dpiCommonCreateParams {
357        let mut params = MaybeUninit::uninit();
358        unsafe {
359            dpiContext_initCommonCreateParams(self.context, params.as_mut_ptr());
360            let mut params = params.assume_init();
361            params.createMode |= DPI_MODE_CREATE_THREADED;
362            params
363        }
364    }
365
366    pub fn conn_create_params(&self) -> dpiConnCreateParams {
367        let mut params = MaybeUninit::uninit();
368        unsafe {
369            dpiContext_initConnCreateParams(self.context, params.as_mut_ptr());
370            params.assume_init()
371        }
372    }
373
374    pub fn pool_create_params(&self) -> dpiPoolCreateParams {
375        let mut params = MaybeUninit::uninit();
376        unsafe {
377            dpiContext_initPoolCreateParams(self.context, params.as_mut_ptr());
378            params.assume_init()
379        }
380    }
381}