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}