oracle/
connection.rs

1// Rust-oracle - Rust binding for Oracle database
2//
3// URL: https://github.com/kubo/rust-oracle
4//
5//-----------------------------------------------------------------------------
6// Copyright (c) 2017-2024 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
16use crate::chkerr;
17use crate::conn::{CloseMode, Info, Purity};
18use crate::error::DPI_ERR_NOT_CONNECTED;
19use crate::oci_attr::data_type::{AttrValue, DataType};
20use crate::oci_attr::handle::ConnHandle;
21use crate::oci_attr::handle::Server;
22use crate::oci_attr::mode::Read;
23use crate::oci_attr::mode::{ReadMode, WriteMode};
24use crate::oci_attr::OciAttr;
25#[cfg(doc)]
26use crate::pool::PoolOptions;
27use crate::sql_type::ObjectType;
28use crate::sql_type::ObjectTypeInternal;
29use crate::sql_type::ToSql;
30use crate::to_rust_str;
31use crate::AssertSend;
32use crate::AssertSync;
33#[cfg(doc)]
34use crate::Batch;
35use crate::BatchBuilder;
36use crate::Context;
37use crate::DpiConn;
38use crate::DpiObjectType;
39use crate::Error;
40use crate::OdpiStr;
41use crate::Result;
42use crate::ResultSet;
43use crate::Row;
44use crate::RowValue;
45use crate::Statement;
46use crate::StatementBuilder;
47use crate::Version;
48use odpic_sys::*;
49use std::borrow::ToOwned;
50use std::collections::HashMap;
51use std::fmt;
52use std::mem::MaybeUninit;
53use std::ptr;
54use std::sync::atomic::{AtomicBool, Ordering};
55use std::sync::Arc;
56use std::sync::Mutex;
57use std::time::Duration;
58
59struct ServerStatus;
60const OCI_ATTR_SERVER_STATUS: u32 = 143;
61const OCI_SERVER_NOT_CONNECTED: u32 = 0;
62const OCI_SERVER_NORMAL: u32 = 1;
63unsafe impl OciAttr for ServerStatus {
64    type HandleType = Server;
65    type Mode = Read;
66    type DataType = u32;
67    const ATTR_NUM: u32 = OCI_ATTR_SERVER_STATUS;
68}
69
70/// Database startup mode
71///
72/// See [`Connection::startup_database`]
73#[derive(Debug, Copy, Clone, PartialEq, Eq)]
74pub enum StartupMode {
75    /// Shuts down a running instance (if there is any) using ABORT before
76    /// starting a new one. This mode should be used only in unusual circumstances.
77    Force,
78
79    /// Allows database access only to users with both the CREATE SESSION
80    /// and RESTRICTED SESSION privileges (normally, the DBA).
81    Restrict,
82}
83
84/// Database shutdown mode
85///
86/// See [`Connection::shutdown_database`].
87#[derive(Debug, Copy, Clone, PartialEq, Eq)]
88pub enum ShutdownMode {
89    /// Further connects are prohibited. Waits for users to disconnect from
90    /// the database.
91    Default,
92
93    /// Further connects are prohibited and no new transactions are allowed.
94    /// Waits for active transactions to complete.
95    Transactional,
96
97    /// Further connects are prohibited and no new transactions are allowed.
98    /// Waits only for local transactions to complete.
99    TransactionalLocal,
100
101    /// Does not wait for current calls to complete or users to disconnect
102    /// from the database. All uncommitted transactions are terminated and
103    /// rolled back.
104    Immediate,
105
106    /// Does not wait for current calls to complete or users to disconnect
107    /// from the database. All uncommitted transactions are terminated and
108    /// are not rolled back. This is the fastest possible way to shut down
109    /// the database, but the next database startup may require instance
110    /// recovery. Therefore, this option should be used only in unusual
111    /// circumstances; for example, if a background process terminates abnormally.
112    Abort,
113
114    /// Shuts down the database. Should be used only in the second call
115    /// to [`Connection::shutdown_database`] after the database is closed and dismounted.
116    Final,
117}
118
119/// [Administrative privilege](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-633842B8-4B19-4F96-A757-783BF62825A7)
120///
121/// See [`Connector::privilege`].
122#[derive(Debug, Copy, Clone, PartialEq, Eq)]
123pub enum Privilege {
124    /// Connects as [SYSDBA](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-BD5D39D1-DBFF-400A-8645-355F8FB9CD31).
125    ///
126    Sysdba,
127
128    /// Connects as [SYSOPER](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-BD5D39D1-DBFF-400A-8645-355F8FB9CD31).
129    Sysoper,
130
131    /// Connects as [SYSASM](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-7396FD18-628B-4026-AA55-79C6D6205EAE) (Oracle 12c or later)
132    Sysasm,
133
134    /// Connects as [SYSBACKUP](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-BF12E37F-4606-42BB-B8B6-4CDC5A870EE7)
135    Sysbackup,
136
137    /// Connects as [SYSDG](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-5798F976-85B2-4973-92F7-DB3F6BC9D497) (Oracle 12c or later)
138    Sysdg,
139
140    /// Connects as [SYSKM](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-573B5831-E106-4D8C-9101-CF9C1B74A39C) (Oracle 12c or later)
141    Syskm,
142
143    /// Connects as [SYSRAC](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-69D0614C-D24E-4EC1-958A-79D7CCA3FA3A) (Oracle 12c R2 or later)
144    Sysrac,
145}
146
147impl Privilege {
148    pub(crate) fn to_dpi(self) -> dpiAuthMode {
149        match self {
150            Privilege::Sysdba => DPI_MODE_AUTH_SYSDBA,
151            Privilege::Sysoper => DPI_MODE_AUTH_SYSOPER,
152            Privilege::Sysasm => DPI_MODE_AUTH_SYSASM,
153            Privilege::Sysbackup => DPI_MODE_AUTH_SYSBKP,
154            Privilege::Sysdg => DPI_MODE_AUTH_SYSDGD,
155            Privilege::Syskm => DPI_MODE_AUTH_SYSKMT,
156            Privilege::Sysrac => DPI_MODE_AUTH_SYSRAC,
157        }
158    }
159}
160
161#[derive(Debug, Copy, Clone, PartialEq, Eq)]
162/// Connection status
163pub enum ConnStatus {
164    /// The connection is alive. See [`Connection::status`] for details.
165    Normal,
166    /// The connection has been terminated. See [`Connection::status`] for details.
167    NotConnected,
168    /// The connection has been closed by [`Connection::close`].
169    Closed,
170}
171
172#[derive(Debug, Default, Clone, PartialEq)]
173pub(crate) struct CommonCreateParamsBuilder {
174    events: bool,
175    edition: Option<String>,
176    driver_name: Option<String>,
177    stmt_cache_size: Option<u32>,
178}
179
180impl CommonCreateParamsBuilder {
181    pub fn events(&mut self, b: bool) {
182        self.events = b;
183    }
184
185    pub fn edition<S>(&mut self, edition: S)
186    where
187        S: Into<String>,
188    {
189        self.edition = Some(edition.into());
190    }
191
192    pub fn driver_name<S>(&mut self, driver_name: S)
193    where
194        S: Into<String>,
195    {
196        self.driver_name = Some(driver_name.into());
197    }
198
199    pub fn stmt_cache_size(&mut self, size: u32) {
200        self.stmt_cache_size = Some(size);
201    }
202
203    pub fn build(&self, ctxt: &Context) -> dpiCommonCreateParams {
204        let mut common_params = ctxt.common_create_params();
205        if self.events {
206            common_params.createMode |= DPI_MODE_CREATE_EVENTS;
207        }
208        if let Some(ref s) = self.edition {
209            let s = OdpiStr::new(s);
210            common_params.edition = s.ptr;
211            common_params.editionLength = s.len;
212        }
213        if let Some(ref s) = self.driver_name {
214            let s = OdpiStr::new(s);
215            common_params.driverName = s.ptr;
216            common_params.driverNameLength = s.len;
217        }
218        if let Some(s) = self.stmt_cache_size {
219            common_params.stmtCacheSize = s;
220        }
221        common_params
222    }
223}
224
225/// Builder data type to create Connection.
226///
227/// When a connection can be established only with username, password
228/// and connect string, use [`Connection::connect`] instead.
229#[derive(Debug, Clone, PartialEq)]
230pub struct Connector {
231    username: String,
232    password: String,
233    connect_string: String,
234    privilege: Option<Privilege>,
235    external_auth: bool,
236    prelim_auth: bool,
237    new_password: String,
238    purity: Option<Purity>,
239    connection_class: String,
240    app_context: Vec<(String, String, String)>,
241    common_params: CommonCreateParamsBuilder,
242}
243
244impl Connector {
245    /// Create a connector
246    pub fn new<U, P, C>(username: U, password: P, connect_string: C) -> Connector
247    where
248        U: Into<String>,
249        P: Into<String>,
250        C: Into<String>,
251    {
252        Connector {
253            username: username.into(),
254            password: password.into(),
255            connect_string: connect_string.into(),
256            privilege: None,
257            external_auth: false,
258            prelim_auth: false,
259            new_password: "".into(),
260            purity: None,
261            connection_class: "".into(),
262            app_context: vec![],
263            common_params: Default::default(),
264        }
265    }
266
267    /// Set [administrative privilege](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-633842B8-4B19-4F96-A757-783BF62825A7).
268    ///
269    /// # Examples
270    ///
271    /// ```no_run
272    /// # use oracle::*;
273    /// // connect system/manager as sysdba
274    /// let conn = Connector::new("system", "manager", "")
275    ///     .privilege(Privilege::Sysdba)
276    ///     .connect()?;
277    /// # Ok::<(), Error>(())
278    /// ```
279    pub fn privilege(&mut self, privilege: Privilege) -> &mut Connector {
280        self.privilege = Some(privilege);
281        self
282    }
283
284    /// Uses external authentication such as [OS authentication][].
285    ///
286    /// # Examples
287    ///
288    /// ```no_run
289    /// # use oracle::*;
290    /// let conn = Connector::new("", "", "")
291    ///     .external_auth(true)
292    ///     .connect()?;
293    /// # Ok::<(), Error>(())
294    /// ```
295    ///
296    /// [OS authentication]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-37BECE32-58D5-43BF-A098-97936D66968F
297    pub fn external_auth(&mut self, b: bool) -> &mut Connector {
298        self.external_auth = b;
299        self
300    }
301
302    /// Sets prelim auth mode to connect to an idle instance.
303    ///
304    /// See [starting up a database](Connection::startup_database).
305    pub fn prelim_auth(&mut self, b: bool) -> &mut Connector {
306        self.prelim_auth = b;
307        self
308    }
309
310    /// Sets new password during establishing a connection.
311    ///
312    /// When a password is expired, you cannot connect to the user.
313    /// A new password must be set by other user or set during establishing
314    /// a connection.
315    ///
316    /// # Examples
317    ///
318    /// Connect to user `scott` with password `tiger`. If the password
319    /// is expired, set a new password `jaguar`.
320    ///
321    /// ```no_run
322    /// # use oracle::*;
323    /// let conn = match Connection::connect("scott", "tiger", "") {
324    ///     Ok(conn) => conn,
325    ///     Err(Error::OciError(dberr)) if dberr.code() == 28001 => {
326    ///         // ORA-28001: the password has expired
327    ///         Connector::new("scott", "tiger", "")
328    ///             .new_password("jaguar")
329    ///             .connect()?
330    ///     }
331    ///     Err(err) => return Err(err),
332    /// };
333    /// # Ok::<(), Error>(())
334    /// ```
335    pub fn new_password<P>(&mut self, password: P) -> &mut Connector
336    where
337        P: Into<String>,
338    {
339        self.new_password = password.into();
340        self
341    }
342
343    /// Sets session purity specifying whether an application can reuse a pooled session (`Purity::Self_`) or must use a new session (`Purity::New`) from [DRCP][] pooled sessions.
344    ///
345    /// [DRCP]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-015CA8C1-2386-4626-855D-CC546DDC1086
346    pub fn purity(&mut self, purity: Purity) -> &mut Connector {
347        self.purity = Some(purity);
348        self
349    }
350
351    /// Sets a connection class to restrict sharing [DRCP][] pooled sessions.
352    ///
353    /// See [here][] for more detail.
354    ///
355    /// [DRCP]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-015CA8C1-2386-4626-855D-CC546DDC1086
356    /// [here]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-EC3DEE61-512C-4CBB-A431-91894D0E1E37
357    pub fn connection_class<S>(&mut self, connection_class: S) -> &mut Connector
358    where
359        S: Into<String>,
360    {
361        self.connection_class = connection_class.into();
362        self
363    }
364
365    /// Appends an application context.
366    ///
367    /// See [Oracle manual](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-5841261E-988F-4A56-A2B4-71114AB3D51D)
368    ///
369    /// This is same with [DBMS_SESSION.SET_CONTEXT][] but this can set application contexts before a connection is established.
370    ///
371    /// [DBMS_SESSION.SET_CONTEXT]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-395C622C-ED79-44CC-9157-6A320934F2A9
372    ///
373    /// # Examples
374    ///
375    /// ```
376    /// # use oracle::{Connector, Error};
377    /// # use oracle::test_util;
378    /// # let username = test_util::main_user();
379    /// # let password = test_util::main_password();
380    /// # let connect_string = test_util::connect_string();
381    /// let conn = Connector::new(username, password, connect_string)
382    ///               .app_context("CLIENTCONTEXT", "foo", "bar")
383    ///               .app_context("CLIENTCONTEXT", "baz", "qux")
384    ///               .connect()?;
385    /// let val = conn.query_row_as::<String>("select sys_context('CLIENTCONTEXT', 'foo') from dual", &[])?;
386    /// assert_eq!(val, "bar");
387    /// let val = conn.query_row_as::<String>("select sys_context('CLIENTCONTEXT', 'baz') from dual", &[])?;
388    /// assert_eq!(val, "qux");
389    /// # Ok::<(), Error>(())
390    /// ```
391    pub fn app_context<T1, T2, T3>(&mut self, namespace: T1, name: T2, value: T3) -> &mut Connector
392    where
393        T1: Into<String>,
394        T2: Into<String>,
395        T3: Into<String>,
396    {
397        self.app_context
398            .push((namespace.into(), name.into(), value.into()));
399        self
400    }
401
402    // Remove later
403    #[doc(hidden)]
404    pub fn tag<S>(&mut self, _tag: S) -> &mut Connector
405    where
406        S: Into<String>,
407    {
408        self
409    }
410
411    // Remove later
412    #[doc(hidden)]
413    pub fn match_any_tag(&mut self, _b: bool) -> &mut Connector {
414        self
415    }
416
417    /// Reserved for when advanced queuing (AQ) or continuous query
418    /// notification (CQN) is supported.
419    pub fn events(&mut self, b: bool) -> &mut Connector {
420        self.common_params.events(b);
421        self
422    }
423
424    /// Specifies edition of [Edition-Based Redefinition][].
425    ///
426    /// [Edition-Based Redefinition]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-58DE05A0-5DEF-4791-8FA8-F04D11964906
427    pub fn edition<S>(&mut self, edition: S) -> &mut Connector
428    where
429        S: Into<String>,
430    {
431        self.common_params.edition(edition);
432        self
433    }
434
435    /// Sets the driver name displayed in [V$SESSION_CONNECT_INFO.CLIENT_DRIVER][].
436    ///
437    /// The default value is "rust-oracle : version number". Only the first 8
438    /// chracters "rust-ora" are displayed when the Oracle server version is
439    /// lower than 12.0.1.2.
440    ///
441    /// [V$SESSION_CONNECT_INFO.CLIENT_DRIVER]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-9F0DCAEA-A67E-4183-89E7-B1555DC591CE
442    pub fn driver_name<S>(&mut self, driver_name: S) -> &mut Connector
443    where
444        S: Into<String>,
445    {
446        self.common_params.driver_name(driver_name);
447        self
448    }
449
450    /// Specifies the number of statements to retain in the statement cache. Use a
451    /// value of 0 to disable the statement cache completely.
452    ///
453    /// The default value is 20.
454    ///
455    /// See also [`Connection::stmt_cache_size`] and [`Connection::set_stmt_cache_size`]
456    pub fn stmt_cache_size(&mut self, size: u32) -> &mut Connector {
457        self.common_params.stmt_cache_size(size);
458        self
459    }
460
461    /// Connect an Oracle server using specified parameters
462    pub fn connect(&self) -> Result<Connection> {
463        let ctxt = Context::new()?;
464        let common_params = self.common_params.build(&ctxt);
465        let (conn_params, _app_contexts) = self.to_dpi_conn_create_params(&ctxt);
466        Connection::connect_internal(
467            ctxt,
468            &self.username,
469            &self.password,
470            &self.connect_string,
471            common_params,
472            conn_params,
473        )
474    }
475
476    fn to_dpi_conn_create_params(
477        &self,
478        ctxt: &Context,
479    ) -> (dpiConnCreateParams, Vec<dpiAppContext>) {
480        let mut conn_params = ctxt.conn_create_params();
481
482        if let Some(ref privilege) = self.privilege {
483            conn_params.authMode |= privilege.to_dpi();
484        }
485        if self.external_auth {
486            conn_params.externalAuth = 1;
487        }
488        if self.prelim_auth {
489            conn_params.authMode |= DPI_MODE_AUTH_PRELIM;
490        }
491        let s = OdpiStr::new(&self.new_password);
492        conn_params.newPassword = s.ptr;
493        conn_params.newPasswordLength = s.len;
494        if let Some(purity) = self.purity {
495            conn_params.purity = purity.to_dpi();
496        }
497        let s = OdpiStr::new(&self.connection_class);
498        conn_params.connectionClass = s.ptr;
499        conn_params.connectionClassLength = s.len;
500        let mut app_context = Vec::with_capacity(self.app_context.len());
501        for ac in &self.app_context {
502            let namespace = OdpiStr::new(&ac.0);
503            let name = OdpiStr::new(&ac.1);
504            let value = OdpiStr::new(&ac.2);
505            app_context.push(dpiAppContext {
506                namespaceName: namespace.ptr,
507                namespaceNameLength: namespace.len,
508                name: name.ptr,
509                nameLength: name.len,
510                value: value.ptr,
511                valueLength: value.len,
512            });
513        }
514        if !app_context.is_empty() {
515            conn_params.appContext = app_context.as_mut_ptr();
516            conn_params.numAppContext = app_context.len() as u32;
517        }
518        (conn_params, app_context)
519    }
520}
521
522pub(crate) type Conn = Arc<InnerConn>;
523
524pub(crate) struct InnerConn {
525    ctxt: Context,
526    pub(crate) handle: DpiConn,
527    pub(crate) autocommit: AtomicBool,
528    pub(crate) objtype_cache: Mutex<HashMap<String, Arc<ObjectTypeInternal>>>,
529    tag: String,
530    tag_found: bool,
531    is_new_connection: bool,
532}
533
534impl InnerConn {
535    pub fn new(
536        ctxt: Context,
537        handle: *mut dpiConn,
538        conn_params: &dpiConnCreateParams,
539    ) -> InnerConn {
540        InnerConn {
541            ctxt,
542            handle: DpiConn::new(handle),
543            autocommit: AtomicBool::new(false),
544            objtype_cache: Mutex::new(HashMap::new()),
545            tag: to_rust_str(conn_params.outTag, conn_params.outTagLength),
546            tag_found: conn_params.outTagFound != 0,
547            is_new_connection: conn_params.outNewSession != 0,
548        }
549    }
550
551    pub(crate) fn ctxt(&self) -> &Context {
552        &self.ctxt
553    }
554
555    pub fn autocommit(&self) -> bool {
556        self.autocommit.load(Ordering::Relaxed)
557    }
558
559    pub fn clear_object_type_cache(&self) -> Result<()> {
560        self.objtype_cache.lock()?.clear();
561        Ok(())
562    }
563}
564
565impl fmt::Debug for InnerConn {
566    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
567        write!(
568            f,
569            "Conn {{ handle: {:?}, autocommit: {:?}",
570            self.handle.raw(),
571            self.autocommit,
572        )?;
573        if !self.tag.is_empty() {
574            write!(f, ", tag: {:?}", self.tag)?;
575        }
576        if self.tag_found {
577            write!(f, ", tag_found: {:?}", self.tag_found)?;
578        }
579        write!(f, " }}")
580    }
581}
582
583/// Connection to an Oracle database
584pub struct Connection {
585    pub(crate) conn: Conn,
586}
587
588impl AssertSync for Connection {}
589impl AssertSend for Connection {}
590
591impl Connection {
592    /// Connects to an Oracle server using username, password and connect string.
593    ///
594    /// If you need to connect the server with additional parameters
595    /// such as SYSDBA privilege, use [`Connector`] instead.
596    ///
597    /// # Examples
598    ///
599    /// Connect to a local database.
600    ///
601    /// ```no_run
602    /// # use oracle::*;
603    /// let conn = Connection::connect("scott", "tiger", "")?;
604    /// # Ok::<(), Error>(())
605    /// ```
606    ///
607    /// Connect to a remote database specified by easy connect naming.
608    ///
609    /// ```no_run
610    /// # use oracle::*;
611    /// let conn = Connection::connect("scott", "tiger",
612    ///                                "server_name:1521/service_name")?;
613    /// # Ok::<(), Error>(())
614    /// ```
615    pub fn connect<U, P, C>(username: U, password: P, connect_string: C) -> Result<Connection>
616    where
617        U: AsRef<str>,
618        P: AsRef<str>,
619        C: AsRef<str>,
620    {
621        let ctxt = Context::new()?;
622        let common_params = ctxt.common_create_params();
623        let conn_params = ctxt.conn_create_params();
624        Connection::connect_internal(
625            ctxt,
626            username.as_ref(),
627            password.as_ref(),
628            connect_string.as_ref(),
629            common_params,
630            conn_params,
631        )
632    }
633
634    fn connect_internal(
635        ctxt: Context,
636        username: &str,
637        password: &str,
638        connect_string: &str,
639        common_params: dpiCommonCreateParams,
640        mut conn_params: dpiConnCreateParams,
641    ) -> Result<Connection> {
642        let username = OdpiStr::new(username);
643        let password = OdpiStr::new(password);
644        let connect_string = OdpiStr::new(connect_string);
645        let mut handle = ptr::null_mut();
646        chkerr!(
647            &ctxt,
648            dpiConn_create(
649                ctxt.context,
650                username.ptr,
651                username.len,
652                password.ptr,
653                password.len,
654                connect_string.ptr,
655                connect_string.len,
656                &common_params,
657                &mut conn_params,
658                &mut handle
659            )
660        );
661        ctxt.set_warning();
662        conn_params.outNewSession = 1;
663        Ok(Connection::from_dpi_handle(ctxt, handle, &conn_params))
664    }
665
666    pub(crate) fn from_conn(conn: Conn) -> Connection {
667        Connection { conn }
668    }
669
670    pub(crate) fn from_dpi_handle(
671        ctxt: Context,
672        handle: *mut dpiConn,
673        params: &dpiConnCreateParams,
674    ) -> Connection {
675        Connection {
676            conn: Arc::new(InnerConn::new(ctxt, handle, params)),
677        }
678    }
679
680    pub(crate) fn ctxt(&self) -> &Context {
681        &self.conn.ctxt
682    }
683
684    pub(crate) fn handle(&self) -> *mut dpiConn {
685        self.conn.handle.raw()
686    }
687
688    /// Closes the connection before the end of lifetime.
689    ///
690    /// This fails when open statements or LOBs exist.
691    pub fn close(&self) -> Result<()> {
692        self.close_with_mode(CloseMode::Default)
693    }
694
695    pub fn close_with_mode(&self, mode: CloseMode) -> Result<()> {
696        let (mode, tag) = match mode {
697            CloseMode::Default => (DPI_MODE_CONN_CLOSE_DEFAULT, ""),
698            CloseMode::Drop => (DPI_MODE_CONN_CLOSE_DROP, ""),
699            CloseMode::Retag(tag) => (DPI_MODE_CONN_CLOSE_RETAG, tag),
700        };
701        let tag = OdpiStr::new(tag);
702        chkerr!(
703            self.ctxt(),
704            dpiConn_close(self.handle(), mode, tag.ptr, tag.len)
705        );
706        Ok(())
707    }
708
709    /// Creates [`StatementBuilder`][] to create a [`Statement`][]
710    ///
711    /// # Examples
712    ///
713    /// Executes a SQL statement with different parameters.
714    ///
715    /// ```no_run
716    /// # use oracle::*;
717    /// # let conn = Connection::connect("scott", "tiger", "")?;
718    /// let mut stmt = conn.statement("insert into emp(empno, ename) values (:id, :name)").build()?;
719    ///
720    /// let emp_list = [
721    ///     (7369, "Smith"),
722    ///     (7499, "Allen"),
723    ///     (7521, "Ward"),
724    /// ];
725    ///
726    /// // insert rows using positional parameters
727    /// for emp in &emp_list {
728    ///    stmt.execute(&[&emp.0, &emp.1])?;
729    /// }
730    ///
731    /// let emp_list = [
732    ///     (7566, "Jones"),
733    ///     (7654, "Martin"),
734    ///     (7698, "Blake"),
735    /// ];
736    ///
737    /// // insert rows using named parameters
738    /// for emp in &emp_list {
739    ///    stmt.execute_named(&[("id", &emp.0), ("name", &emp.1)])?;
740    /// }
741    /// # Ok::<(), Error>(())
742    /// ```
743    ///
744    /// Query methods in Connection allocate memory for 100 rows by default
745    /// to reduce the number of network round trips in case that many rows are
746    /// fetched. When 100 isn't preferable, use [`StatementBuilder::fetch_array_size`]
747    /// to customize it.
748    ///
749    /// ```no_run
750    /// # use oracle::*;
751    /// # let conn = Connection::connect("scott", "tiger", "")?;
752    /// // fetch top 10 rows.
753    /// let mut stmt = conn
754    ///     .statement("select empno, ename from emp order by empno fetch first 10 rows only")
755    ///     .fetch_array_size(10)
756    ///     .build()?;
757    /// for row_result in stmt.query_as::<(i32, String)>(&[])? {
758    ///     let (empno, ename) = row_result?;
759    ///     println!("empno: {}, ename: {}", empno, ename);
760    /// }
761    /// # Ok::<(), Error>(())
762    /// ```
763    ///
764    /// By default, a maximum of 2 rows are returned when the query is first
765    /// executed. To modify this, use [`StatementBuilder::prefetch_rows`] to customize
766    /// it. For more information on the difference between this and `fetch_array_size`,
767    /// see [this writeup](https://blog.dbi-services.com/arraysize-or-rowprefetch-in-sqlplus/)
768    /// or [this description](https://oracle.github.io/node-oracledb/doc/api.html#rowfetching).
769    ///
770    /// ```no_run
771    /// # use oracle::*;
772    /// # let conn = Connection::connect("scott", "tiger", "")?;
773    /// // fetch top 10 rows.
774    /// let mut stmt = conn
775    ///     .statement("select empno, ename from emp order by empno fetch first 10 rows only")
776    ///     .prefetch_rows(11)  // add one to avoid a round-trip to check for end-of-fetch
777    ///     .build()?;
778    /// for row_result in stmt.query_as::<(i32, String)>(&[])? {
779    ///     let (empno, ename) = row_result?;
780    ///     println!("empno: {}, ename: {}", empno, ename);
781    /// }
782    /// # Ok::<(), Error>(())
783    /// ```
784    pub fn statement<'conn, 'sql>(&'conn self, sql: &'sql str) -> StatementBuilder<'conn, 'sql> {
785        StatementBuilder::new(self, sql)
786    }
787
788    /// Creates [BatchBuilder][]
789    ///
790    /// See [`Batch`].
791    pub fn batch<'conn, 'sql>(
792        &'conn self,
793        sql: &'sql str,
794        max_batch_size: usize,
795    ) -> BatchBuilder<'conn, 'sql> {
796        BatchBuilder::new(self, sql, max_batch_size)
797    }
798
799    /// Executes a select statement and returns a result set containing [`Row`]s.
800    ///
801    /// See [Query Methods][].
802    ///
803    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
804    pub fn query(&self, sql: &str, params: &[&dyn ToSql]) -> Result<ResultSet<'static, Row>> {
805        let mut stmt = self.statement(sql).build()?;
806        stmt.exec(params, true, "query")?;
807        Ok(ResultSet::<Row>::from_stmt(stmt.stmt))
808    }
809
810    /// Executes a select statement using named parameters and returns a result set containing [`Row`]s.
811    ///
812    /// See [Query Methods][].
813    ///
814    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
815    pub fn query_named(
816        &self,
817        sql: &str,
818        params: &[(&str, &dyn ToSql)],
819    ) -> Result<ResultSet<'static, Row>> {
820        let mut stmt = self.statement(sql).build()?;
821        stmt.exec_named(params, true, "query_named")?;
822        Ok(ResultSet::<Row>::from_stmt(stmt.stmt))
823    }
824
825    /// Executes a select statement and returns a result set containing [`RowValue`]s.
826    ///
827    /// See [Query Methods][].
828    ///
829    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
830    pub fn query_as<T>(&self, sql: &str, params: &[&dyn ToSql]) -> Result<ResultSet<'static, T>>
831    where
832        T: RowValue,
833    {
834        let mut stmt = self.statement(sql).build()?;
835        stmt.exec(params, true, "query_as")?;
836        Ok(ResultSet::<T>::from_stmt(stmt.stmt))
837    }
838
839    /// Executes a select statement using named parameters and returns a result set containing [`RowValue`]s.
840    ///
841    /// See [Query Methods][].
842    ///
843    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
844    pub fn query_as_named<T>(
845        &self,
846        sql: &str,
847        params: &[(&str, &dyn ToSql)],
848    ) -> Result<ResultSet<'static, T>>
849    where
850        T: RowValue,
851    {
852        let mut stmt = self.statement(sql).build()?;
853        stmt.exec_named(params, true, "query_as_named")?;
854        Ok(ResultSet::<T>::from_stmt(stmt.stmt))
855    }
856
857    /// Gets one row from a query using positoinal bind parameters.
858    ///
859    /// See [Query Methods][].
860    ///
861    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
862    pub fn query_row(&self, sql: &str, params: &[&dyn ToSql]) -> Result<Row> {
863        let mut stmt = self.statement(sql).fetch_array_size(1).build()?;
864        stmt.query_row(params)?;
865        Ok(stmt.stmt.row.take().unwrap())
866    }
867
868    /// Gets one row from a query using named bind parameters.
869    ///
870    /// See [Query Methods][].
871    ///
872    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
873    pub fn query_row_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<Row> {
874        let mut stmt = self.statement(sql).fetch_array_size(1).build()?;
875        stmt.query_row_named(params)?;
876        Ok(stmt.stmt.row.take().unwrap())
877    }
878
879    /// Gets one row from a query as specified type.
880    ///
881    /// See [Query Methods][].
882    ///
883    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
884    pub fn query_row_as<T>(&self, sql: &str, params: &[&dyn ToSql]) -> Result<T>
885    where
886        T: RowValue,
887    {
888        let mut stmt = self.statement(sql).fetch_array_size(1).build()?;
889        stmt.query_row_as::<T>(params)
890    }
891
892    /// Gets one row from a query with named bind parameters as specified type.
893    ///
894    /// See [Query Methods][].
895    ///
896    /// [Query Methods]: https://github.com/kubo/rust-oracle/blob/master/docs/query-methods.md
897    pub fn query_row_as_named<T>(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<T>
898    where
899        T: RowValue,
900    {
901        let mut stmt = self.statement(sql).fetch_array_size(1).build()?;
902        stmt.query_row_as_named::<T>(params)
903    }
904
905    /// Creates a statement, binds values by position and executes it in one call.
906    /// It will retunrs `Err` when the statemnet is a select statement.
907    ///
908    /// # Examples
909    ///
910    /// ```no_run
911    /// # use oracle::*;
912    /// let conn = Connection::connect("scott", "tiger", "")?;
913    ///
914    /// // execute a statement without bind parameters
915    /// conn.execute("insert into emp(empno, ename) values (113, 'John')", &[])?;
916    ///
917    /// // execute a statement with binding parameters by position
918    /// conn.execute("insert into emp(empno, ename) values (:1, :2)", &[&114, &"Smith"])?;
919    ///
920    /// # Ok::<(), Error>(())
921    /// ```
922    pub fn execute(&self, sql: &str, params: &[&dyn ToSql]) -> Result<Statement> {
923        let mut stmt = self.statement(sql).build()?;
924        stmt.execute(params)?;
925        Ok(stmt)
926    }
927
928    /// Creates a statement, binds values by name and executes it in one call.
929    /// It will retunrs `Err` when the statemnet is a select statement.
930    ///
931    /// The bind variable names are compared case-insensitively.
932    ///
933    /// # Examples
934    ///
935    /// ```no_run
936    /// # use oracle::*;
937    /// let conn = Connection::connect("scott", "tiger", "")?;
938    ///
939    /// // execute a statement with binding parameters by name
940    /// conn.execute_named("insert into emp(empno, ename) values (:id, :name)",
941    ///                    &[("id", &114),
942    ///                      ("name", &"Smith")])?;
943    ///
944    /// # Ok::<(), Error>(())
945    /// ```
946    pub fn execute_named(&self, sql: &str, params: &[(&str, &dyn ToSql)]) -> Result<Statement> {
947        let mut stmt = self.statement(sql).build()?;
948        stmt.execute_named(params)?;
949        Ok(stmt)
950    }
951
952    /// Commits the current active transaction
953    pub fn commit(&self) -> Result<()> {
954        chkerr!(self.ctxt(), dpiConn_commit(self.handle()));
955        Ok(())
956    }
957
958    /// Rolls back the current active transaction
959    pub fn rollback(&self) -> Result<()> {
960        chkerr!(self.ctxt(), dpiConn_rollback(self.handle()));
961        Ok(())
962    }
963
964    /// Gets autocommit mode.
965    /// It is false by default.
966    pub fn autocommit(&self) -> bool {
967        self.conn.autocommit()
968    }
969
970    /// Enables or disables autocommit mode.
971    /// It is disabled by default.
972    pub fn set_autocommit(&mut self, autocommit: bool) {
973        self.conn.autocommit.store(autocommit, Ordering::Relaxed)
974    }
975
976    /// Cancels execution of running statements in the connection
977    ///
978    /// # Examples
979    ///
980    /// ```
981    /// # use oracle::Error;
982    /// # use oracle::test_util;
983    /// # use std::sync::Arc;
984    /// # use std::thread::{self, sleep};
985    /// # use std::time::{Duration, Instant};
986    /// # let conn = test_util::connect()?;
987    ///
988    /// // Wrap conn with Arc to be share it with threads.
989    /// let conn = Arc::new(conn);
990    ///
991    /// let now = Instant::now();
992    /// let range = Duration::from_secs(3)..=Duration::from_secs(20);
993    ///
994    /// // Start a thread to cancel a query
995    /// let cloned_conn = conn.clone();
996    /// let join_handle = thread::spawn(move || {
997    ///   sleep(Duration::from_secs(3));
998    ///   cloned_conn.break_execution()
999    /// });
1000    ///
1001    /// // This query is canceled by break_execution.
1002    /// let result = conn.query_row_as::<u64>("select count(*) from all_objects, all_objects, all_objects, all_objects, all_objects", &[]);
1003    /// assert!(result.is_err());
1004    /// let elapsed = now.elapsed();
1005    /// assert!(range.contains(&elapsed), "cancel: {:?}, {:?}", elapsed, result.unwrap_err());
1006    /// # join_handle.join().unwrap();
1007    /// # Ok::<(), Error>(())
1008    /// ```
1009    pub fn break_execution(&self) -> Result<()> {
1010        chkerr!(self.ctxt(), dpiConn_breakExecution(self.handle()));
1011        Ok(())
1012    }
1013
1014    /// Gets an object type information from name
1015    ///
1016    /// ```
1017    /// # use oracle::Error;
1018    /// # use oracle::test_util;
1019    /// # let conn = test_util::connect()?;
1020    /// let objtype = conn.object_type("SDO_GEOMETRY")?;
1021    /// assert_eq!(objtype.schema(), "MDSYS");
1022    /// assert_eq!(objtype.name(), "SDO_GEOMETRY");
1023    /// # Ok::<(), Error>(())
1024    /// ```
1025    ///
1026    /// Note that the object type is cached in the connection.
1027    /// However when "CREATE TYPE", "ALTER TYPE" or "DROP TYPE"
1028    /// is executed, the cache clears.
1029    pub fn object_type(&self, name: &str) -> Result<ObjectType> {
1030        {
1031            let guard = self.conn.objtype_cache.lock()?;
1032            if let Some(rc_objtype) = guard.get(name) {
1033                return Ok(ObjectType {
1034                    internal: rc_objtype.clone(),
1035                });
1036            }
1037        }
1038        let s = OdpiStr::new(name);
1039        let mut handle = ptr::null_mut();
1040        chkerr!(
1041            self.ctxt(),
1042            dpiConn_getObjectType(self.handle(), s.ptr, s.len, &mut handle)
1043        );
1044        let res = ObjectType::from_dpi_object_type(self.conn.clone(), DpiObjectType::new(handle));
1045        if let Ok(ref objtype) = res {
1046            self.conn
1047                .objtype_cache
1048                .lock()?
1049                .insert(name.to_string(), objtype.internal.clone());
1050        };
1051        res
1052    }
1053
1054    /// Clear the object type cache in the connection.
1055    ///
1056    /// See also [`object_type`](#method.object_type).
1057    pub fn clear_object_type_cache(&self) -> Result<()> {
1058        self.conn.clear_object_type_cache()
1059    }
1060
1061    #[doc(hidden)]
1062    pub fn object_type_cache_len(&self) -> usize {
1063        self.conn.objtype_cache.lock().unwrap().len()
1064    }
1065
1066    /// Gets information about the server version
1067    ///
1068    /// NOTE: if you connect to Oracle Database 18 or higher with
1069    /// Oracle client libraries 12.2 or lower, it gets the base
1070    /// version (such as 18.0.0.0.0) instead of the full version
1071    /// (such as 18.3.0.0.0).
1072    ///
1073    /// # Examples
1074    ///
1075    /// ```
1076    /// # use oracle::Error;
1077    /// # use oracle::test_util;
1078    /// # let conn = test_util::connect()?;
1079    /// let (version, banner) = conn.server_version()?;
1080    /// println!("Oracle Version: {}", version);
1081    /// println!("--- Version Banner ---");
1082    /// println!("{}", banner);
1083    /// println!("---------------------");
1084    /// # Ok::<(), Error>(())
1085    /// ```
1086    pub fn server_version(&self) -> Result<(Version, String)> {
1087        let mut s = OdpiStr::new("");
1088        let mut ver = MaybeUninit::uninit();
1089        chkerr!(
1090            self.ctxt(),
1091            dpiConn_getServerVersion(self.handle(), &mut s.ptr, &mut s.len, ver.as_mut_ptr())
1092        );
1093        Ok((
1094            Version::new_from_dpi_ver(unsafe { ver.assume_init() }),
1095            s.to_string(),
1096        ))
1097    }
1098
1099    /// Changes the password for the specified user
1100    pub fn change_password(
1101        &self,
1102        username: &str,
1103        old_password: &str,
1104        new_password: &str,
1105    ) -> Result<()> {
1106        let username = OdpiStr::new(username);
1107        let old_password = OdpiStr::new(old_password);
1108        let new_password = OdpiStr::new(new_password);
1109        chkerr!(
1110            self.ctxt(),
1111            dpiConn_changePassword(
1112                self.handle(),
1113                username.ptr,
1114                username.len,
1115                old_password.ptr,
1116                old_password.len,
1117                new_password.ptr,
1118                new_password.len
1119            )
1120        );
1121        Ok(())
1122    }
1123
1124    /// Pings the connection to see if it is still alive.
1125    ///
1126    /// It checks the connection by making a network round-trip
1127    /// between the client and the server.
1128    ///
1129    /// See also [`Connection::status`].
1130    pub fn ping(&self) -> Result<()> {
1131        chkerr!(self.ctxt(), dpiConn_ping(self.handle()));
1132        Ok(())
1133    }
1134
1135    /// Gets the status of the connection.
1136    ///
1137    /// It returns `Ok(ConnStatus::Closed)` when the connection was closed
1138    /// by [`Connection::close`].
1139    /// Otherwise see bellow.
1140    ///
1141    /// **Oracle client 12.2 and later:**
1142    ///
1143    /// It checks whether the underlying TCP socket has disconnected
1144    /// by the server. There is no guarantee that the server is alive
1145    /// and the network between the client and server has no trouble.
1146    ///
1147    /// For example, it returns `Ok(ConnStatus::NotConnected)` when the
1148    /// database on the server-side OS stopped and the client received
1149    /// a FIN or RST packet. However it returns `Ok(ConnStatus::Normal)`
1150    /// when the server-side OS itself crashes or the network is in
1151    /// trouble.
1152    ///
1153    /// **Oracle client 11.2 and 12.1:**
1154    ///
1155    /// It returns `Ok(ConnStatus::Normal)` when the last network
1156    /// round-trip between the client and server went through. Otherwise,
1157    /// `Ok(ConnStatus::NotConnected)`. There is no guarantee that the
1158    /// next network round-trip will go through.
1159    ///
1160    /// See also [`Connection::ping`].
1161    pub fn status(&self) -> Result<ConnStatus> {
1162        match self.oci_attr::<ServerStatus>() {
1163            Ok(status) => match status {
1164                OCI_SERVER_NOT_CONNECTED => Ok(ConnStatus::NotConnected),
1165                OCI_SERVER_NORMAL => Ok(ConnStatus::Normal),
1166                _ => Err(Error::internal_error(format!(
1167                    "unexpected server status {}",
1168                    status
1169                ))),
1170            },
1171            Err(err) if err.dpi_code() == Some(DPI_ERR_NOT_CONNECTED) => Ok(ConnStatus::Closed),
1172            Err(err) => Err(err),
1173        }
1174    }
1175
1176    /// Get the warning when connecting to the database or executing SQL statements.
1177    ///
1178    /// This is available to check the following two cases.
1179    ///
1180    /// 1. The user account status is in grace period of password expiration. See [Password Change Life Cycle].
1181    /// 2. A stored procedure is created with PL/SQL compilation errors.
1182    ///
1183    /// ```no_run
1184    /// # use oracle::{Connection, Error};
1185    /// # use oracle::test_util;
1186    /// # let username = test_util::main_user();
1187    /// # let password = test_util::main_password();
1188    /// # let connect_string = test_util::connect_string();
1189    /// let conn = Connection::connect(username, password, connect_string)?;
1190    ///
1191    /// // Check warning when connecting to the database.
1192    /// // This must be done before any SQL execution. Otherwise it is cleared.
1193    /// if let Some(Error::OciError(dberr)) = conn.last_warning() {
1194    ///   if dberr.code() == 28002 {
1195    ///      println!("{}", dberr.message()); // => "the password will expire within n days."
1196    ///   }
1197    /// }
1198    /// # Ok::<(), Error>(())
1199    /// ```
1200    ///
1201    /// ```
1202    /// # use oracle::{Connector, Error};
1203    /// # use oracle::test_util;
1204    /// # let conn = test_util::connect()?;
1205    /// // create a procedure with compilation error
1206    /// let sql = "create or replace procedure my_proc is begin; null; end;";
1207    /// conn.execute(sql, &[])?;
1208    /// match conn.last_warning() {
1209    ///     Some(Error::OciError(dberr)) if dberr.code() == 24344 => (),
1210    ///     warn => panic!("Unexpected last warning: {:?}", warn),
1211    /// }
1212    ///
1213    /// // create a procedure without compilation error
1214    /// let sql = "create or replace procedure my_proc is begin null; end;";
1215    /// conn.execute(sql, &[])?;
1216    /// // The last warning is cleared when a SQL statement is executed successfully without any warning.
1217    /// match conn.last_warning() {
1218    ///     None => (),
1219    ///     warn => panic!("Unexpected last warning: {:?}", warn),
1220    /// }
1221    /// # Ok::<(), Error>(())
1222    /// ```
1223    ///
1224    /// [Password Change Life Cycle]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-BED74427-BC63-4095-8829-1AF68411FC6B
1225    pub fn last_warning(&self) -> Option<Error> {
1226        self.ctxt().last_warning().map(Error::oci_error)
1227    }
1228
1229    /// Gets the statement cache size
1230    ///
1231    /// See also [`Connector::stmt_cache_size`]
1232    pub fn stmt_cache_size(&self) -> Result<u32> {
1233        let mut size = 0u32;
1234        chkerr!(
1235            self.ctxt(),
1236            dpiConn_getStmtCacheSize(self.handle(), &mut size)
1237        );
1238        Ok(size)
1239    }
1240
1241    /// Sets the statement cache size
1242    ///
1243    /// See also [`Connector::stmt_cache_size`]
1244    pub fn set_stmt_cache_size(&self, size: u32) -> Result<()> {
1245        chkerr!(self.ctxt(), dpiConn_setStmtCacheSize(self.handle(), size));
1246        Ok(())
1247    }
1248
1249    /// Gets the current call timeout used for round-trips to
1250    /// the database made with this connection. `None` means that no timeouts
1251    /// will take place.
1252    pub fn call_timeout(&self) -> Result<Option<Duration>> {
1253        let mut value = 0;
1254        chkerr!(
1255            self.ctxt(),
1256            dpiConn_getCallTimeout(self.handle(), &mut value)
1257        );
1258        if value != 0 {
1259            Ok(Some(Duration::from_millis(value.into())))
1260        } else {
1261            Ok(None)
1262        }
1263    }
1264
1265    /// Sets the call timeout to be used for round-trips to the
1266    /// database made with this connection. None means that no timeouts
1267    /// will take place.
1268    ///
1269    /// The call timeout value applies to each database round-trip
1270    /// individually, not to the sum of all round-trips. Time spent
1271    /// processing in rust-oracle before or after the completion of each
1272    /// round-trip is not counted.
1273    ///
1274    /// - If the time from the start of any one round-trip to the
1275    ///   completion of that same round-trip exceeds call timeout,
1276    ///   then the operation is halted and an exception occurs.
1277    ///
1278    /// - In the case where an rust-oracle operation requires more than one
1279    ///   round-trip and each round-trip takes less than call timeout,
1280    ///   then no timeout will occur, even if the sum of all round-trip
1281    ///   calls exceeds call timeout.
1282    ///
1283    /// - If no round-trip is required, the operation will never be
1284    ///   interrupted.
1285    ///
1286    /// After a timeout is triggered, rust-oracle attempts to clean up the
1287    /// internal connection state. The cleanup is allowed to take another
1288    /// `duration`.
1289    ///
1290    /// If the cleanup was successful, an exception DPI-1067 will be
1291    /// raised but the application can continue to use the connection.
1292    ///
1293    /// For small values of call timeout, the connection cleanup may not
1294    /// complete successfully within the additional call timeout
1295    /// period. In this case an exception ORA-3114 is raised and the
1296    /// connection will no longer be usable. It should be closed.
1297    ///
1298    /// # Examples
1299    ///
1300    /// ```
1301    /// # use oracle::Error;
1302    /// # use oracle::test_util;
1303    /// # use std::time::{Duration, Instant};
1304    /// # let conn = test_util::connect()?;
1305    /// // Set timeout three seconds.
1306    /// conn.set_call_timeout(Some(Duration::from_millis(3_000)))?;
1307    ///
1308    /// let now = Instant::now();
1309    /// let range = Duration::from_millis(2_900)..=Duration::from_millis(20_000);
1310    ///
1311    /// // This query is canceled by timeout.
1312    /// let result = conn.query_row_as::<u64>("select count(*) from all_objects, all_objects, all_objects, all_objects, all_objects", &[]);
1313    /// assert!(result.is_err());
1314    /// let elapsed = now.elapsed();
1315    /// assert!(range.contains(&elapsed), "cancel: {:?}, {:?}", elapsed, result.unwrap_err());
1316    /// # Ok::<(), Error>(())
1317    /// ```
1318    pub fn set_call_timeout(&self, dur: Option<Duration>) -> Result<()> {
1319        if let Some(dur) = dur {
1320            let msecs = dur.as_millis().try_into().map_err(|_| {
1321                Error::out_of_range(format!(
1322                    "too long duration {:?}. It must be less than 49.7 days",
1323                    dur
1324                ))
1325            })?;
1326            if msecs == 0 {
1327                return Err(Error::out_of_range(format!(
1328                    "too short duration {:?}. It must not be submilliseconds",
1329                    dur
1330                )));
1331            }
1332            chkerr!(self.ctxt(), dpiConn_setCallTimeout(self.handle(), msecs));
1333        } else {
1334            chkerr!(self.ctxt(), dpiConn_setCallTimeout(self.handle(), 0));
1335        }
1336        Ok(())
1337    }
1338
1339    /// Gets current schema associated with the connection
1340    pub fn current_schema(&self) -> Result<String> {
1341        let mut s = OdpiStr::new("");
1342        chkerr!(
1343            self.ctxt(),
1344            dpiConn_getCurrentSchema(self.handle(), &mut s.ptr, &mut s.len)
1345        );
1346        Ok(s.to_string())
1347    }
1348
1349    /// Sets current schema associated with the connection
1350    ///
1351    /// `conn.set_current_schema("MDSYS")` has same effect with the following SQL.
1352    ///
1353    /// ```sql
1354    /// ALTER SESSION SET CURRENT_SCHEMA = MDSYS;
1355    /// ```
1356    ///
1357    /// # Examples
1358    ///
1359    /// ```
1360    /// # use oracle::Error;
1361    /// # use oracle::test_util;
1362    /// # let conn1 = test_util::connect()?;
1363    /// # let conn2 = test_util::connect()?;
1364    ///
1365    /// // Get the username and sid of connection 1.
1366    /// let (username, sid) = conn1.query_row_as::<(String, i32)>("select user, sys_context('userenv', 'sid') from dual", &[])?;
1367    ///
1368    /// // Create a closure to get the schema name of connection 1 in the database side using connection 2.
1369    /// let mut stmt = conn2.statement("select schemaname from v$session where sid = :1").build()?;
1370    /// let mut schema_name = move || { stmt.query_row_as::<String>(&[&sid]) };
1371    ///
1372    /// // The default current schema is same with the username.
1373    /// assert_eq!(schema_name()?, username);
1374    ///
1375    /// // Change the current schema of connection 1.
1376    /// let new_schema_name = "MDSYS";
1377    /// conn1.set_current_schema(new_schema_name)?;
1378    ///
1379    /// // The current schema of connection 1 in the database side has not been changed yet.
1380    /// assert_eq!(schema_name()?, username);
1381    ///
1382    /// // Call a function sending packets to the database server.
1383    /// // The new schema name is prepended to the packets.
1384    /// let _ = conn1.query_row_as::<i32>("select 1 from dual", &[]);
1385    ///
1386    /// // The current schema of connection 1 in the database side is changed.
1387    /// assert_eq!(schema_name()?, new_schema_name);
1388    ///
1389    /// # Ok::<(), Error>(())
1390    /// ```
1391    pub fn set_current_schema(&self, current_schema: &str) -> Result<()> {
1392        let s = OdpiStr::new(current_schema);
1393        chkerr!(
1394            self.ctxt(),
1395            dpiConn_setCurrentSchema(self.handle(), s.ptr, s.len)
1396        );
1397        Ok(())
1398    }
1399
1400    /// Gets edition associated with the connection
1401    pub fn edition(&self) -> Result<String> {
1402        let mut s = OdpiStr::new("");
1403        chkerr!(
1404            self.ctxt(),
1405            dpiConn_getEdition(self.handle(), &mut s.ptr, &mut s.len)
1406        );
1407        Ok(s.to_string())
1408    }
1409
1410    /// Gets external name associated with the connection
1411    pub fn external_name(&self) -> Result<String> {
1412        let mut s = OdpiStr::new("");
1413        chkerr!(
1414            self.ctxt(),
1415            dpiConn_getExternalName(self.handle(), &mut s.ptr, &mut s.len)
1416        );
1417        Ok(s.to_string())
1418    }
1419
1420    /// Sets external name associated with the connection
1421    pub fn set_external_name(&self, external_name: &str) -> Result<()> {
1422        let s = OdpiStr::new(external_name);
1423        chkerr!(
1424            self.ctxt(),
1425            dpiConn_setExternalName(self.handle(), s.ptr, s.len)
1426        );
1427        Ok(())
1428    }
1429
1430    /// Gets internal name associated with the connection
1431    pub fn internal_name(&self) -> Result<String> {
1432        let mut s = OdpiStr::new("");
1433        chkerr!(
1434            self.ctxt(),
1435            dpiConn_getInternalName(self.handle(), &mut s.ptr, &mut s.len)
1436        );
1437        Ok(s.to_string())
1438    }
1439
1440    /// Sets internal name associated with the connection
1441    pub fn set_internal_name(&self, internal_name: &str) -> Result<()> {
1442        let s = OdpiStr::new(internal_name);
1443        chkerr!(
1444            self.ctxt(),
1445            dpiConn_setInternalName(self.handle(), s.ptr, s.len)
1446        );
1447        Ok(())
1448    }
1449
1450    /// Sets module associated with the connection
1451    ///
1452    /// This is same with calling [DBMS_APPLICATION_INFO.SET_MODULE][] but
1453    /// without executing a statement. The module name is piggybacked
1454    /// to the server with the next network round-trip.
1455    ///
1456    /// [DBMS_APPLICATION_INFO.SET_MODULE]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B2E2BD20-D91D-40DB-A3F6-37A853384F30
1457    pub fn set_module(&self, module: &str) -> Result<()> {
1458        let s = OdpiStr::new(module);
1459        chkerr!(self.ctxt(), dpiConn_setModule(self.handle(), s.ptr, s.len));
1460        Ok(())
1461    }
1462
1463    /// Sets action associated with the connection
1464    ///
1465    /// This is same with calling [DBMS_APPLICATION_INFO.SET_ACTION][] but
1466    /// without executing a statement. The action name is piggybacked
1467    /// to the server with the next network round-trip.
1468    ///
1469    /// [DBMS_APPLICATION_INFO.SET_ACTION]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-90DA860F-BFBE-4539-BA00-2279B02B8F26
1470    pub fn set_action(&self, action: &str) -> Result<()> {
1471        let s = OdpiStr::new(action);
1472        chkerr!(self.ctxt(), dpiConn_setAction(self.handle(), s.ptr, s.len));
1473        Ok(())
1474    }
1475
1476    /// Sets client info associated with the connection
1477    ///
1478    /// This is same with calling [DBMS_APPLICATION_INFO.SET_CLIENT_INFO][] but
1479    /// without executing a statement. The client info is piggybacked
1480    /// to the server with the next network round-trip.
1481    ///
1482    /// [DBMS_APPLICATION_INFO.SET_CLIENT_INFO]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-68A3DF04-BE91-46CC-8D2B-97BA0E89956F
1483    pub fn set_client_info(&self, client_info: &str) -> Result<()> {
1484        let s = OdpiStr::new(client_info);
1485        chkerr!(
1486            self.ctxt(),
1487            dpiConn_setClientInfo(self.handle(), s.ptr, s.len)
1488        );
1489        Ok(())
1490    }
1491
1492    /// Sets client identifier associated with the connection
1493    ///
1494    /// This is same with calling [DBMS_SESSION.SET_IDENTIFIER][] but
1495    /// without executing a statement. The client identifier is piggybacked
1496    /// to the server with the next network round-trip.
1497    ///
1498    /// [DBMS_SESSION.SET_IDENTIFIER]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-988EA930-BDFE-4205-A806-E54F05333562
1499    pub fn set_client_identifier(&self, client_identifier: &str) -> Result<()> {
1500        let s = OdpiStr::new(client_identifier);
1501        chkerr!(
1502            self.ctxt(),
1503            dpiConn_setClientIdentifier(self.handle(), s.ptr, s.len)
1504        );
1505        Ok(())
1506    }
1507
1508    /// Sets name of the database operation to be monitored in the database.
1509    /// Sets to `''` if you want to end monitoring the current running database operation.
1510    ///
1511    /// This is same with calling [DBMS_SQL_MONITOR.BEGIN_OPERATION][] but
1512    /// without executing a statement. The database operation name is piggybacked
1513    /// to the server with the next network round-trip.
1514    ///
1515    /// See [Monitoring Database Operations][] in Oracle Database SQL Tuning Guide
1516    ///
1517    /// [db_op]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-9CE3C342-D210-4690-A7E9-5813EF9D558E
1518    /// [DBMS_SQL_MONITOR.BEGIN_OPERATION]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-25BE0E79-3A19-4303-9F66-2CFDB87C7F82
1519    /// [Monitoring Database Operations]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-C941CE9D-97E1-42F8-91ED-4949B2B710BF
1520    pub fn set_db_op(&self, db_op: &str) -> Result<()> {
1521        let s = OdpiStr::new(db_op);
1522        chkerr!(self.ctxt(), dpiConn_setDbOp(self.handle(), s.ptr, s.len));
1523        Ok(())
1524    }
1525
1526    /// Starts up a database
1527    ///
1528    /// This corresponds to sqlplus command `startup nomount`.
1529    /// You need to connect the databas as system privilege in prelim_auth
1530    /// mode in advance.
1531    /// After this method is executed, you need to reconnect the server
1532    /// as system privilege *without* prelim_auth and executes
1533    /// `alter database mount` and then `alter database open`.
1534    ///
1535    /// # Examples
1536    ///
1537    /// Connect to an idle instance as sysdba and start up a database
1538    ///
1539    /// ```no_run
1540    /// # use oracle::*;
1541    /// // connect as sysdba with prelim_auth mode
1542    /// let conn = Connector::new("sys", "change_on_install", "")
1543    ///     .privilege(Privilege::Sysdba)
1544    ///     .prelim_auth(true)
1545    ///     .connect()?;
1546    ///
1547    /// // start the instance
1548    /// conn.startup_database(&[])?;
1549    /// conn.close()?;
1550    ///
1551    /// // connect again without prelim_auth
1552    /// let conn = Connector::new("sys", "change_on_install", "")
1553    ///     .privilege(Privilege::Sysdba)
1554    ///     .connect()?;
1555    ///
1556    /// // mount and open a database
1557    /// conn.execute("alter database mount", &[])?;
1558    /// conn.execute("alter database open", &[])?;
1559    /// # Ok::<(), Error>(())
1560    /// ```
1561    ///
1562    /// Start up a database in restricted mode
1563    ///
1564    /// ```ignore
1565    /// ...
1566    /// conn.startup_database(&[StartupMode::Restrict])?;
1567    /// ...
1568    /// ```
1569    ///
1570    /// If the database is running, shut it down with mode ABORT and then
1571    /// start up in restricted mode
1572    ///
1573    /// ```ignore
1574    /// ...
1575    /// conn.startup_database(&[StartupMode::Force, StartupMode::Restrict])?;
1576    /// ...
1577    /// ```
1578    pub fn startup_database(&self, modes: &[StartupMode]) -> Result<()> {
1579        let mut mode_num = 0;
1580        for mode in modes {
1581            mode_num |= match *mode {
1582                StartupMode::Force => DPI_MODE_STARTUP_FORCE,
1583                StartupMode::Restrict => DPI_MODE_STARTUP_RESTRICT,
1584            };
1585        }
1586        chkerr!(
1587            self.ctxt(),
1588            dpiConn_startupDatabase(self.handle(), mode_num)
1589        );
1590        Ok(())
1591    }
1592
1593    /// Shuts down a database
1594    ///
1595    /// When this method is called with [`ShutdownMode::Default`],
1596    /// [`ShutdownMode::Transactional`], [`ShutdownMode::TransactionalLocal`]
1597    /// or [`ShutdownMode::Immediate`], execute "alter database close normal"
1598    /// and "alter database dismount" and call this method again with
1599    /// [`ShutdownMode::Final`].
1600    ///
1601    /// When this method is called with [`ShutdownMode::Abort`],
1602    /// the database is aborted immediately.
1603    ///
1604    /// # Examples
1605    ///
1606    /// Same with `shutdown immediate` on sqlplus.
1607    ///
1608    /// ```no_run
1609    /// # use oracle::*;
1610    /// // connect as sysdba
1611    /// let conn = Connector::new("sys", "change_on_install", "")
1612    ///     .privilege(Privilege::Sysdba)
1613    ///     .connect()?;
1614    ///
1615    /// // begin 'shutdown immediate'
1616    /// conn.shutdown_database(ShutdownMode::Immediate)?;
1617    ///
1618    /// // close and dismount the database
1619    /// conn.execute("alter database close normal", &[])?;
1620    /// conn.execute("alter database dismount", &[])?;
1621    ///
1622    /// // finish shutdown
1623    /// conn.shutdown_database(ShutdownMode::Final)?;
1624    /// # Ok::<(), Error>(())
1625    /// ```
1626    ///
1627    /// Same with `shutdown abort` on sqlplus.
1628    ///
1629    /// ```no_run
1630    /// # use oracle::*;
1631    /// // connect as sysdba
1632    /// let conn = Connector::new("sys", "change_on_install", "")
1633    ///     .privilege(Privilege::Sysdba).connect()?;
1634    ///
1635    /// // 'shutdown abort'
1636    /// conn.shutdown_database(ShutdownMode::Abort)?;
1637    ///
1638    /// // The database is aborted here.
1639    /// # Ok::<(), Error>(())
1640    /// ```
1641    pub fn shutdown_database(&self, mode: ShutdownMode) -> Result<()> {
1642        let mode = match mode {
1643            ShutdownMode::Default => DPI_MODE_SHUTDOWN_DEFAULT,
1644            ShutdownMode::Transactional => DPI_MODE_SHUTDOWN_TRANSACTIONAL,
1645            ShutdownMode::TransactionalLocal => DPI_MODE_SHUTDOWN_TRANSACTIONAL_LOCAL,
1646            ShutdownMode::Immediate => DPI_MODE_SHUTDOWN_IMMEDIATE,
1647            ShutdownMode::Abort => DPI_MODE_SHUTDOWN_ABORT,
1648            ShutdownMode::Final => DPI_MODE_SHUTDOWN_FINAL,
1649        };
1650        chkerr!(self.ctxt(), dpiConn_shutdownDatabase(self.handle(), mode));
1651        Ok(())
1652    }
1653
1654    /// Gets the tag of the connection that was acquired from a connection pool.
1655    /// It is `""` if the connection is a standalone one or not tagged.
1656    ///
1657    /// # Examples
1658    ///
1659    /// ```
1660    /// # use oracle::{Connection, Error};
1661    /// # use oracle::pool::{PoolBuilder, PoolOptions};
1662    /// # use oracle::conn;
1663    /// # use oracle::test_util;
1664    /// # let username = test_util::main_user();
1665    /// # let username = &username;
1666    /// # let password = test_util::main_password();
1667    /// # let password = &password;
1668    /// # let connect_string = test_util::connect_string();
1669    /// # let connect_string = &connect_string;
1670    ///
1671    /// // standalone connection
1672    /// let conn = Connection::connect(username, password, connect_string)?;
1673    /// assert_eq!(conn.tag(), "");
1674    /// assert_eq!(conn.tag_found(), false);
1675    ///
1676    /// let pool = PoolBuilder::new(username, password, connect_string)
1677    ///     .build()?;
1678    /// let opts = PoolOptions::new().tag("NAME=VALUE");
1679    ///
1680    /// // No connections with tag "NAME=VALUE" exist in the pool at first.
1681    /// let conn = pool.get_with_options(&opts)?;
1682    /// assert_eq!(conn.tag(), "");
1683    /// assert_eq!(conn.tag_found(), false);
1684    /// // Close the connection with setting a new tag.
1685    /// conn.close_with_mode(conn::CloseMode::Retag("NAME=VALUE"))?;
1686    ///
1687    /// // One connection with tag "NAME=VALUE" exists in the pool now.
1688    /// let conn = pool.get_with_options(&opts)?;
1689    /// assert_eq!(conn.tag_found(), true);
1690    /// assert_eq!(conn.tag(), "NAME=VALUE");
1691    /// # Ok::<(), Error>(())
1692    /// ```
1693    pub fn tag(&self) -> &str {
1694        &self.conn.tag
1695    }
1696
1697    /// Gets `true` when the connection is a standalone one
1698    /// or it is a connection with the specified tag by
1699    /// [`PoolOptions::tag`].
1700    ///
1701    /// Sea also [`Connection::tag`].
1702    pub fn tag_found(&self) -> bool {
1703        self.conn.tag_found
1704    }
1705
1706    /// Returns `true` when the connection is a standalone one
1707    /// or a newly created one by a connection pool.
1708    ///
1709    /// # Examples
1710    ///
1711    /// ```
1712    /// # use oracle::Connection;
1713    /// # use oracle::Error;
1714    /// # use oracle::pool::PoolBuilder;
1715    /// # use oracle::test_util;
1716    /// # let username = test_util::main_user();
1717    /// # let username = &username;
1718    /// # let password = test_util::main_password();
1719    /// # let password = &password;
1720    /// # let connect_string = test_util::connect_string();
1721    /// # let connect_string = &connect_string;
1722    ///
1723    /// let conn = Connection::connect(username, password, connect_string)?;
1724    /// assert!(conn.is_new_connection(), "standalone connection");
1725    ///
1726    /// let pool = PoolBuilder::new(username, password, connect_string)
1727    ///     .build()?;
1728    ///
1729    /// let conn = pool.get()?; // Get a newly created connection
1730    /// assert!(conn.is_new_connection(), "new connectoin from the pool");
1731    /// conn.close()?; // Back the connection to the pool
1732    ///
1733    /// let conn = pool.get()?; // Get a connection cached in the pool
1734    /// assert!(!conn.is_new_connection(), "cached connectoin in the pool");
1735    /// conn.close()?;
1736    /// # Ok::<(), Error>(())
1737    /// ```
1738    pub fn is_new_connection(&self) -> bool {
1739        self.conn.is_new_connection
1740    }
1741
1742    /// Returns information about the connection
1743    ///
1744    /// # Examples
1745    ///
1746    /// ```
1747    /// # use oracle::Error;
1748    /// # use oracle::test_util;
1749    /// # let conn = test_util::connect()?;
1750    /// let info = conn.info()?;
1751    /// let instance_name = conn.query_row_as::<String>("SELECT SYS_CONTEXT('USERENV', 'INSTANCE_NAME') FROM DUAL", &[])?;
1752    /// assert!(info.instance_name.eq_ignore_ascii_case(&instance_name));
1753    /// let service_name = conn.query_row_as::<String>("SELECT SYS_CONTEXT('USERENV', 'SERVICE_NAME') FROM DUAL", &[])?;
1754    /// assert_eq!(info.service_name, service_name);
1755    /// # Ok::<(), Error>(())
1756    /// ```
1757    pub fn info(&self) -> Result<Info> {
1758        let mut info = MaybeUninit::uninit();
1759        chkerr!(
1760            self.ctxt(),
1761            dpiConn_getInfo(self.handle(), info.as_mut_ptr())
1762        );
1763        Info::from_dpi(unsafe { &info.assume_init() })
1764    }
1765
1766    /// Gets an OCI handle attribute corresponding to the specified type parameter
1767    /// See the [`oci_attr` module][crate::oci_attr] for details.
1768    pub fn oci_attr<T>(&self) -> Result<<<T::DataType as DataType>::Type as ToOwned>::Owned>
1769    where
1770        T: OciAttr,
1771        T::HandleType: ConnHandle,
1772        T::Mode: ReadMode,
1773    {
1774        let attr_value = AttrValue::from_conn(self, <T::HandleType>::HANDLE_TYPE, <T>::ATTR_NUM);
1775        unsafe { <T::DataType>::get(attr_value) }
1776    }
1777
1778    /// Sets an OCI handle attribute corresponding to the specified type parameter
1779    /// See the [`oci_attr` module][crate::oci_attr] for details.
1780    pub fn set_oci_attr<T>(&mut self, value: &<T::DataType as DataType>::Type) -> Result<()>
1781    where
1782        T: OciAttr,
1783        T::HandleType: ConnHandle,
1784        T::Mode: WriteMode,
1785    {
1786        let mut attr_value =
1787            AttrValue::from_conn(self, <T::HandleType>::HANDLE_TYPE, <T>::ATTR_NUM);
1788        unsafe { <T::DataType>::set(&mut attr_value, value) }
1789    }
1790}
1791
1792impl Drop for Connection {
1793    fn drop(&mut self) {
1794        let _ = self.clear_object_type_cache();
1795    }
1796}
1797
1798impl fmt::Debug for Connection {
1799    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1800        write!(f, "Connection {{ conn: {:?}", self.conn)
1801    }
1802}