oracle/
pool.rs

1// Rust-oracle - Rust binding for Oracle database
2//
3// URL: https://github.com/kubo/rust-oracle
4//
5//-----------------------------------------------------------------------------
6// Copyright (c) 2017-2021 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//! Type definitions for connection pooling
17//!
18//! This module defines types for connection pooling using [Session Pooling in OCI].
19//!
20//! [Session Pooling in OCI]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-F9662FFB-EAEF-495C-96FC-49C6D1D9625C
21use crate::chkerr;
22use crate::conn::Purity;
23use crate::connection::CommonCreateParamsBuilder;
24use crate::AssertSend;
25use crate::AssertSync;
26use crate::Connection;
27use crate::Context;
28use crate::DpiPool;
29use crate::Error;
30use crate::OdpiStr;
31use crate::Privilege;
32use crate::Result;
33use odpic_sys::*;
34use std::convert::TryInto;
35use std::fmt;
36use std::ptr;
37use std::time::Duration;
38
39/// The mode to use when closing pools
40///
41/// See [`Pool::close`].
42#[derive(Debug, Copy, Clone, PartialEq, Eq)]
43pub enum CloseMode {
44    /// If there are any active connections in the pool an
45    /// error is returned.
46    Default,
47
48    /// Causes all of the active connections in the pool
49    /// to be closed before closing the pool itself.
50    Force,
51}
52
53impl CloseMode {
54    fn to_dpi_value(self) -> dpiPoolCloseMode {
55        match self {
56            CloseMode::Default => DPI_MODE_POOL_CLOSE_DEFAULT,
57            CloseMode::Force => DPI_MODE_POOL_CLOSE_FORCE,
58        }
59    }
60}
61
62/// The mode to use when getting connections from a connection pool
63#[derive(Debug, Copy, Clone, PartialEq, Eq)]
64pub enum GetMode {
65    /// The caller should block until a
66    /// connection is available from the pool.
67    Wait,
68
69    /// The caller should return
70    /// immediately, regardless of whether a connection is
71    /// available in the pool. If a connection is not
72    /// available an error is returned.
73    NoWait,
74
75    /// A new connection should be created if
76    /// all of the connections in the pool are busy, even if
77    /// this exceeds the maximum connections allowable for
78    /// the connection pool (see [`PoolBuilder::max_connections`])
79    ForceGet,
80
81    /// The caller should block until a
82    /// connection is available from the pool, but only for
83    /// the specified length of time defined in
84    /// the tuple field. If a
85    /// connection is not available within the specified
86    /// period of time an error is returned.
87    TimedWait(Duration),
88}
89
90impl GetMode {
91    fn to_dpi_value(self) -> dpiPoolGetMode {
92        match self {
93            GetMode::Wait => DPI_MODE_POOL_GET_WAIT as dpiPoolGetMode,
94            GetMode::NoWait => DPI_MODE_POOL_GET_NOWAIT as dpiPoolGetMode,
95            GetMode::ForceGet => DPI_MODE_POOL_GET_FORCEGET as dpiPoolGetMode,
96            GetMode::TimedWait(_) => DPI_MODE_POOL_GET_TIMEDWAIT as dpiPoolGetMode,
97        }
98    }
99
100    fn to_wait_timeout(self) -> Result<Option<u32>> {
101        if let GetMode::TimedWait(ref dur) = self {
102            match dur.as_millis().try_into() {
103                Ok(msecs) => Ok(Some(msecs)),
104                Err(err) => Err(Error::out_of_range(format!(
105                    "too long timed wait duration {:?}",
106                    dur
107                ))
108                .add_source(err)),
109            }
110        } else {
111            Ok(None)
112        }
113    }
114}
115
116/// Whether a connection pool is homogeneous or heterogeneous.
117#[derive(Debug, Copy, Clone, PartialEq, Eq)]
118pub enum PoolType {
119    /// The default pool type.
120    /// All connections in the pool are authenticated with the
121    /// username and password provided during pool creation.
122    Homogeneous,
123
124    /// Connections with different authentication contexts can be
125    /// created in the same pool. This pool type also supports
126    /// external authentication.
127    /// [`PoolBuilder::min_connections`] and [`PoolBuilder::connection_increment`]
128    /// are ignored in this type.
129    Heterogeneous,
130}
131
132#[derive(Clone, Copy, Debug, PartialEq)]
133struct I32Seconds(i32);
134
135impl I32Seconds {
136    fn try_from(dur: Option<Duration>, msg: &str) -> Result<I32Seconds> {
137        if let Some(dur) = dur {
138            match dur.as_secs().try_into() {
139                Ok(secs) => Ok(I32Seconds(secs)),
140                Err(err) => {
141                    Err(Error::out_of_range(format!("too long {} {:?}", msg, dur)).add_source(err))
142                }
143            }
144        } else {
145            Ok(I32Seconds(-1))
146        }
147    }
148
149    fn into(self) -> Option<Duration> {
150        match self.0.try_into() {
151            Ok(secs) => Some(Duration::from_secs(secs)),
152            Err(_) => None,
153        }
154    }
155}
156
157/// Additional options to get a connection from a pool
158///
159/// This is used as the argument of [`Pool::get_with_options`].
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub struct PoolOptions {
162    username: String,
163    password: String,
164    privilege: Option<Privilege>,
165    external_auth: bool,
166    tag: String,
167    match_any_tag: bool,
168    purity: Option<Purity>,
169    connection_class: String,
170}
171
172impl PoolOptions {
173    pub fn new() -> PoolOptions {
174        PoolOptions {
175            username: "".into(),
176            password: "".into(),
177            privilege: None,
178            external_auth: false,
179            tag: "".into(),
180            match_any_tag: false,
181            purity: None,
182            connection_class: "".into(),
183        }
184    }
185
186    pub fn username<S>(mut self, username: S) -> Self
187    where
188        S: Into<String>,
189    {
190        self.username = username.into();
191        self
192    }
193
194    pub fn password<S>(mut self, password: S) -> Self
195    where
196        S: Into<String>,
197    {
198        self.password = password.into();
199        self
200    }
201
202    pub fn privilege(mut self, privilege: Privilege) -> Self {
203        self.privilege = Some(privilege);
204        self
205    }
206
207    pub fn external_auth(mut self, enable: bool) -> Self {
208        self.external_auth = enable;
209        self
210    }
211
212    pub fn tag<S>(mut self, tag: S) -> Self
213    where
214        S: Into<String>,
215    {
216        self.tag = tag.into();
217        self
218    }
219
220    pub fn match_any_tag(mut self, enable: bool) -> Self {
221        self.match_any_tag = enable;
222        self
223    }
224
225    pub fn purity(mut self, purity: Purity) -> Self {
226        self.purity = Some(purity);
227        self
228    }
229
230    pub fn connection_class<S>(mut self, connection_class: S) -> Self
231    where
232        S: Into<String>,
233    {
234        self.connection_class = connection_class.into();
235        self
236    }
237
238    fn to_dpi_conn_create_params(&self, ctxt: &Context) -> dpiConnCreateParams {
239        let mut conn_params = ctxt.conn_create_params();
240
241        if let Some(privilege) = self.privilege {
242            conn_params.authMode |= privilege.to_dpi();
243        }
244        conn_params.externalAuth = i32::from(self.external_auth);
245        let s = OdpiStr::new(&self.tag);
246        conn_params.tag = s.ptr;
247        conn_params.tagLength = s.len;
248        conn_params.matchAnyTag = i32::from(self.match_any_tag);
249        if let Some(purity) = self.purity {
250            conn_params.purity = purity.to_dpi();
251        }
252        let s = OdpiStr::new(&self.connection_class);
253        conn_params.connectionClass = s.ptr;
254        conn_params.connectionClassLength = s.len;
255        conn_params
256    }
257}
258
259impl Default for PoolOptions {
260    fn default() -> Self {
261        Self::new()
262    }
263}
264
265#[derive(Clone, Copy, Debug, PartialEq)]
266struct U32Seconds(u32);
267
268impl U32Seconds {
269    fn try_from(dur: Duration, msg: &str) -> Result<U32Seconds> {
270        match dur.as_secs().try_into() {
271            Ok(secs) => Ok(U32Seconds(secs)),
272            Err(err) => {
273                Err(Error::out_of_range(format!("too long {} {:?}", msg, dur)).add_source(err))
274            }
275        }
276    }
277}
278
279#[derive(Clone, Copy, Debug, PartialEq)]
280struct U32Milliseconds(u32);
281
282impl U32Milliseconds {
283    fn try_from(dur: Duration, msg: &str) -> Result<U32Milliseconds> {
284        match dur.as_millis().try_into() {
285            Ok(secs) => Ok(U32Milliseconds(secs)),
286            Err(err) => {
287                Err(Error::out_of_range(format!("too long {} {:?}", msg, dur)).add_source(err))
288            }
289        }
290    }
291}
292
293/// A bulider to make a connection pool
294#[derive(Debug, Clone, PartialEq)]
295pub struct PoolBuilder {
296    username: String,
297    password: String,
298    connect_string: String,
299    min_connections: Option<u32>,
300    max_connections: Option<u32>,
301    connection_increment: Option<u32>,
302    ping_interval: Option<I32Seconds>,
303    ping_timeout: Option<U32Milliseconds>,
304    homogeneous: Option<i32>,
305    external_auth: Option<bool>,
306    get_mode: Option<GetMode>,
307    timeout: Option<U32Seconds>,
308    max_lifetime_connection: Option<U32Seconds>,
309    plsql_fixup_callback: Option<String>,
310    max_connections_per_shard: Option<u32>,
311    common_params: CommonCreateParamsBuilder,
312}
313
314impl PoolBuilder {
315    /// Creates a builder to make a connection pool.
316    pub fn new<U, P, C>(username: U, password: P, connect_string: C) -> PoolBuilder
317    where
318        U: Into<String>,
319        P: Into<String>,
320        C: Into<String>,
321    {
322        PoolBuilder {
323            username: username.into(),
324            password: password.into(),
325            connect_string: connect_string.into(),
326            min_connections: None,
327            max_connections: None,
328            connection_increment: None,
329            ping_interval: None,
330            ping_timeout: None,
331            homogeneous: None,
332            external_auth: None,
333            get_mode: None,
334            timeout: None,
335            max_lifetime_connection: None,
336            plsql_fixup_callback: None,
337            max_connections_per_shard: None,
338            common_params: Default::default(),
339        }
340    }
341
342    /// Specifies the minimum number of connections to be created by the connection pool.
343    /// This value is ignored if the pool type is [`PoolType::Heterogeneous`].
344    /// The default value is 1.
345    ///
346    /// See also [`Pool::reconfigure`].
347    pub fn min_connections(&mut self, num: u32) -> &mut PoolBuilder {
348        self.min_connections = Some(num);
349        self
350    }
351
352    /// Specifies the maximum number of connections that can be created by the connection
353    /// pool. Values of 1 and higher are acceptable. The default value is 1.
354    ///
355    /// See also [`Pool::reconfigure`].
356    pub fn max_connections(&mut self, num: u32) -> &mut PoolBuilder {
357        self.max_connections = Some(num);
358        self
359    }
360
361    /// Specifies the number of connections that will be created by the connection pool
362    /// when more connections are required and the number of connection is less than
363    /// the maximum allowed. This value is ignored if the pool type is [`PoolType::Heterogeneous`].
364    /// This value added to the [`PoolBuilder::min_connections`] value
365    /// must not exceed the [`PoolBuilder::max_connections`].
366    /// The default value is 0.
367    ///
368    /// See also [`Pool::reconfigure`].
369    pub fn connection_increment(&mut self, num: u32) -> &mut PoolBuilder {
370        self.connection_increment = Some(num);
371        self
372    }
373
374    /// Specifies the length of time since a connection has last been used
375    /// before a ping will be performed to verify that the connection is still
376    /// valid. A `None` value disables this check. The default value is 60 seconds.
377    ///
378    /// See also [`Pool::ping_interval`] and [`Pool::set_ping_interval`].
379    pub fn ping_interval(&mut self, dur: Option<Duration>) -> Result<&mut PoolBuilder> {
380        self.ping_interval = Some(I32Seconds::try_from(dur, "ping interval")?);
381        Ok(self)
382    }
383
384    /// Specifies the length of time to wait when performing a ping to
385    /// verify the connection is still valid before the connection is considered
386    /// invalid and is dropped. The default value is 5 seconds.
387    pub fn ping_timeout(&mut self, dur: Duration) -> Result<&mut PoolBuilder> {
388        self.ping_timeout = Some(U32Milliseconds::try_from(dur, "ping timeout")?);
389        Ok(self)
390    }
391
392    /// Specifies whether the pool is homogeneous or heterogeneous. In a homogeneous pool all
393    /// connections use the same credentials whereas in a heterogeneous pool other
394    /// credentials are permitted. The default value is [`PoolType::Homogeneous`].
395    pub fn pool_type(&mut self, pool_type: PoolType) -> &mut PoolBuilder {
396        self.homogeneous = Some(match pool_type {
397            PoolType::Homogeneous => 1,
398            PoolType::Heterogeneous => 0,
399        });
400        self
401    }
402
403    /// Specifies whether external authentication should be used to create the
404    /// connections in the pool. If this value is `false`, the user name and password values
405    /// must be specified in the call to [`PoolBuilder::new`]; otherwise, the
406    /// username and password values must be empty. The default
407    /// value is `false`. External authentication cannot be used with homogeneous pools.
408    pub fn external_auth(&mut self, b: bool) -> &mut PoolBuilder {
409        self.external_auth = Some(b);
410        self
411    }
412
413    /// Specifies the mode to use when connections are acquired from the pool.
414    /// The default value is [`GetMode::NoWait`].
415    ///
416    /// See also [`Pool::get_mode`] and [`Pool::set_get_mode`].
417    pub fn get_mode(&mut self, mode: GetMode) -> &mut PoolBuilder {
418        self.get_mode = Some(mode);
419        self
420    }
421
422    /// Specifies the length of time after which idle connections in the
423    /// pool are terminated. Note that termination only occurs when the pool is
424    /// accessed. The default value is [`Duration::ZERO`] which means that no idle connections are
425    /// terminated.
426    ///
427    /// See also [`Pool::timeout`] and [`Pool::set_timeout`].
428    pub fn timeout(&mut self, dur: Duration) -> Result<&mut PoolBuilder> {
429        self.timeout = Some(U32Seconds::try_from(dur, "timeout")?);
430        Ok(self)
431    }
432
433    /// Specifies the maximum length of time a pooled connection may
434    /// exist. Connections in use will not be closed. They become candidates for
435    /// termination only when they are released back to the pool and have existed
436    /// for longer than max_lifetime_connection. Connection termination only occurs
437    /// when the pool is accessed. The default value is [`Duration::ZERO`] which means that there is
438    /// no maximum length of time that a pooled connection may exist.
439    ///
440    /// See also [`Pool::max_lifetime_connection`] and [`Pool::set_max_lifetime_connection`].
441    pub fn max_lifetime_connection(&mut self, dur: Duration) -> Result<&mut PoolBuilder> {
442        self.max_lifetime_connection = Some(U32Seconds::try_from(dur, "max lifetime connection")?);
443        Ok(self)
444    }
445
446    /// Specifies the name of a PL/SQL procedure in the format
447    /// *schema.package.callback_proc* which will be called when a connection is
448    /// checked out from the pool and the requested tag doesn't match the actual
449    /// tag assigned to the connection. The procedure accepts the desired
450    /// and actual tags as parameters and it is the responsibility of the procedure
451    /// to ensure that the connection matches the desired state upon completion. See
452    /// the [OCI documentation](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B853A020-752F-494A-8D88-D0396EF57177)
453    /// for more information. This functionality is only available when Oracle Client
454    /// is at version 12.2 and higher.
455    pub fn plsql_fixup_callback<T>(&mut self, plsql: T) -> &mut PoolBuilder
456    where
457        T: Into<String>,
458    {
459        self.plsql_fixup_callback = Some(plsql.into());
460        self
461    }
462
463    /// Specifies the maximum number of connections that can be created by the connection
464    /// pool for each shard in a sharded database. Set this attribute to a value
465    /// other than zero to ensure that the pool is balanced towards each shard. A
466    /// value of zero will not set any maximum number of connections for each shard.
467    /// If the Oracle client library version is less than 18.3, this value is
468    /// ignored.
469    ///
470    /// See also [`Pool::max_connections_per_shard`] and [`Pool::set_max_connections_per_shard`].
471    pub fn max_connections_per_shard(&mut self, num: u32) -> &mut PoolBuilder {
472        self.max_connections_per_shard = Some(num);
473        self
474    }
475
476    fn to_dpi_pool_create_params(&self, ctxt: &Context) -> Result<dpiPoolCreateParams> {
477        let mut pool_params = ctxt.pool_create_params();
478
479        if let Some(val) = self.min_connections {
480            pool_params.minSessions = val;
481        }
482        if let Some(val) = self.max_connections {
483            pool_params.maxSessions = val;
484        }
485        if let Some(val) = self.connection_increment {
486            pool_params.sessionIncrement = val;
487        }
488        if let Some(val) = self.ping_interval {
489            pool_params.pingInterval = val.0;
490        }
491        if let Some(val) = self.ping_timeout {
492            pool_params.pingTimeout = val.0 as i32;
493        }
494        if let Some(val) = self.homogeneous {
495            pool_params.homogeneous = val;
496        }
497        if let Some(val) = self.external_auth {
498            pool_params.externalAuth = i32::from(val);
499        }
500        if let Some(val) = self.get_mode {
501            pool_params.getMode = val.to_dpi_value();
502            if let Some(wait_timeout) = val.to_wait_timeout()? {
503                pool_params.waitTimeout = wait_timeout;
504            }
505        }
506        if let Some(val) = self.timeout {
507            pool_params.timeout = val.0;
508        }
509        if let Some(val) = self.max_lifetime_connection {
510            pool_params.maxLifetimeSession = val.0;
511        }
512        if let Some(ref val) = self.plsql_fixup_callback {
513            let s = OdpiStr::new(val);
514            pool_params.plsqlFixupCallback = s.ptr;
515            pool_params.plsqlFixupCallbackLength = s.len;
516        }
517        if let Some(val) = self.max_connections_per_shard {
518            pool_params.maxSessionsPerShard = val;
519        }
520        Ok(pool_params)
521    }
522
523    /// Reserved for when advanced queuing (AQ) or continuous query
524    /// notification (CQN) is supported.
525    pub fn events(&mut self, b: bool) -> &mut PoolBuilder {
526        self.common_params.events(b);
527        self
528    }
529
530    /// Specifies edition of [Edition-Based Redefinition][].
531    ///
532    /// [Edition-Based Redefinition]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-58DE05A0-5DEF-4791-8FA8-F04D11964906
533    pub fn edition<S>(&mut self, edition: S) -> &mut PoolBuilder
534    where
535        S: Into<String>,
536    {
537        self.common_params.edition(edition);
538        self
539    }
540
541    /// Sets the driver name displayed in [V$SESSION_CONNECT_INFO.CLIENT_DRIVER][].
542    ///
543    /// The default value is "rust-oracle : version number". Only the first 8
544    /// chracters "rust-ora" are displayed when the Oracle server version is
545    /// lower than 12.0.1.2.
546    ///
547    /// [V$SESSION_CONNECT_INFO.CLIENT_DRIVER]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-9F0DCAEA-A67E-4183-89E7-B1555DC591CE
548    pub fn driver_name<S>(&mut self, driver_name: S) -> &mut PoolBuilder
549    where
550        S: Into<String>,
551    {
552        self.common_params.driver_name(driver_name);
553        self
554    }
555
556    /// Specifies the number of statements to retain in the statement cache. Use a
557    /// value of 0 to disable the statement cache completely.
558    /// The default value is 20.
559    ///
560    /// See also [`Pool::stmt_cache_size`] and [`Pool::set_stmt_cache_size`].
561    pub fn stmt_cache_size(&mut self, size: u32) -> &mut PoolBuilder {
562        self.common_params.stmt_cache_size(size);
563        self
564    }
565
566    /// Make a connection pool
567    pub fn build(&self) -> Result<Pool> {
568        let ctxt = Context::new0()?;
569        let username = OdpiStr::new(&self.username);
570        let password = OdpiStr::new(&self.password);
571        let connect_string = OdpiStr::new(&self.connect_string);
572        let common_params = self.common_params.build(&ctxt);
573        let mut pool_params = self.to_dpi_pool_create_params(&ctxt)?;
574        let mut handle = ptr::null_mut();
575        chkerr!(
576            &ctxt,
577            dpiPool_create(
578                ctxt.context,
579                username.ptr,
580                username.len,
581                password.ptr,
582                password.len,
583                connect_string.ptr,
584                connect_string.len,
585                &common_params,
586                &mut pool_params,
587                &mut handle
588            )
589        );
590        Ok(Pool {
591            ctxt,
592            handle: DpiPool::new(handle),
593        })
594    }
595}
596
597/// Connection pool
598///
599///
600/// # Examples
601///
602/// Get connections in a pool
603///
604/// ```
605/// # use oracle::Error;
606/// # use oracle::pool::PoolBuilder;
607/// # use oracle::test_util;
608/// # let username = test_util::main_user();
609/// # let password = test_util::main_password();
610/// # let connect_string = test_util::connect_string();
611/// // Create a pool
612/// let pool = PoolBuilder::new(username, password, connect_string)
613///     .max_connections(20)
614///     .build()?;
615///
616/// // Get connections from the pool.
617/// let conn1 = pool.get()?;
618/// let conn2 = pool.get()?;
619///
620/// assert_eq!(pool.open_count()?, 2); // Two connections are in the pool.
621/// assert_eq!(pool.busy_count()?, 2); // Two connections are in use.
622///
623/// // Return the connections to the pool.
624/// conn1.close()?;
625/// conn2.close()?;
626///
627/// assert_eq!(pool.open_count()?, 2); // Two connections are in the pool.
628/// assert_eq!(pool.busy_count()?, 0); // No connections are in use.
629/// # Ok::<(), Error>(())
630/// ```
631///
632/// Use a [heterogeneous pool](PoolType::Heterogeneous) to pool different users' connections
633///
634/// ```
635/// # use oracle::Error;
636/// # use oracle::Connector;
637/// # use oracle::pool::{PoolBuilder, PoolOptions, PoolType};
638/// # use oracle::test_util;
639/// # let username = test_util::main_user();
640/// # let username = username.as_ref();
641/// # let password = test_util::main_password();
642/// # let another_username = test_util::edition_user();
643/// # let another_username = another_username.as_ref();
644/// # let another_password = test_util::edition_password();
645/// # let connect_string = test_util::connect_string();
646/// // Create a pool
647/// let pool = PoolBuilder::new(username, password, connect_string)
648///     .pool_type(PoolType::Heterogeneous)
649///     .max_connections(20)
650///     .build()?;
651///
652/// // Get a connection from the pool.
653/// let conn1 = pool.get()?;
654/// let conn1_user = conn1.query_row_as::<String>("select lower(user) from dual", &[])?;
655/// assert_eq!(conn1_user, username);
656///
657/// // Get an another user's connection.
658/// let opts = PoolOptions::new().username(another_username).password(another_password);
659/// let conn2 = pool.get_with_options(&opts)?; // with connector to pass username and password
660/// let conn2_user = conn2.query_row_as::<String>("select lower(user) from dual", &[])?;
661/// assert_eq!(conn2_user, another_username);
662/// # Ok::<(), Error>(())
663/// ```
664///
665/// Get connections with tags (`NAME=VALUE` pairs)
666///
667/// ```
668/// # use oracle::Error;
669/// # use oracle::Connector;
670/// # use oracle::conn;
671/// # use oracle::pool::{PoolBuilder, PoolOptions};
672/// # use oracle::test_util;
673/// # let username = test_util::main_user();
674/// # let password = test_util::main_password();
675/// # let connect_string = test_util::connect_string();
676/// // Create a pool
677/// let pool = PoolBuilder::new(username, password, connect_string)
678///     .max_connections(20)
679///     .build()?;
680///
681/// // Create Pool options to specify a tag.
682/// let opts = PoolOptions::new().tag("LANG=FRENCH");
683///
684/// // Get a connection with tag "LANG=FRENCH".
685/// // There are no connections with the tag at this point.
686/// let conn = pool.get_with_options(&opts)?;
687/// assert_eq!(conn.tag_found(), false, "conn.tag_found() (1)"); // new connection
688/// // Change the nls_language for later.
689/// if !conn.tag_found() {
690///   conn.execute("alter session set nls_language = FRENCH", &[])?;
691/// }
692/// // ...
693/// // use the connection
694/// // ...
695/// // return it to the pool with new tag.
696/// conn.close_with_mode(conn::CloseMode::Retag("LANG=FRENCH"))?;
697///
698/// // Get a connection with tag "LANG=FRENCH" again.
699/// // There is one connection with the tag at this point.
700/// let conn = pool.get_with_options(&opts)?;
701/// assert_eq!(conn.tag_found(), true, "conn.tag_found() (2)");
702/// assert_eq!(conn.tag(), "LANG=FRENCH", "conn.tag() (2)");
703/// // Check whether this is the connection previously
704/// let sql = "select value from nls_session_parameters where parameter = 'NLS_LANGUAGE'";
705/// assert_eq!(conn.query_row_as::<String>(sql, &[])?, "FRENCH");
706/// // ...
707/// // The connection has been tagged already. There is no need to set a new tag.
708/// conn.close()?;
709///
710/// # Ok::<(), Error>(())
711/// ```
712#[derive(Clone)]
713pub struct Pool {
714    ctxt: Context,
715    handle: DpiPool,
716}
717
718impl Pool {
719    fn handle(&self) -> *mut dpiPool {
720        self.handle.raw()
721    }
722
723    fn ctxt(&self) -> &Context {
724        &self.ctxt
725    }
726
727    /// Gets a connection from the pool with default parameters.
728    ///
729    /// Use [`Pool::get_with_options`] to get a new one with
730    /// additional parameters.
731    ///
732    /// When the connection is dropped, it backs to the pool
733    /// for subsequent calls to this function. The connection
734    /// can be returned back to the pool earlier by calling
735    /// [`Connection::close`].
736    pub fn get(&self) -> Result<Connection> {
737        self.get_with_options(&PoolOptions::new())
738    }
739
740    /// Acquires a connection from the specified connection pool.
741    ///
742    /// See also [`Pool::get`].
743    pub fn get_with_options(&self, options: &PoolOptions) -> Result<Connection> {
744        let ctxt = Context::new()?;
745        let username = OdpiStr::new(&options.username);
746        let password = OdpiStr::new(&options.password);
747        let mut conn_params = options.to_dpi_conn_create_params(&ctxt);
748        let mut handle = ptr::null_mut();
749        chkerr!(
750            &ctxt,
751            dpiPool_acquireConnection(
752                self.handle(),
753                username.ptr,
754                username.len,
755                password.ptr,
756                password.len,
757                &mut conn_params,
758                &mut handle
759            )
760        );
761        ctxt.set_warning();
762        Ok(Connection::from_dpi_handle(ctxt, handle, &conn_params))
763    }
764
765    /// Closes the pool and makes it unusable for further activity.
766    pub fn close(&self, mode: &CloseMode) -> Result<()> {
767        chkerr!(
768            self.ctxt(),
769            dpiPool_close(self.handle(), mode.to_dpi_value())
770        );
771        Ok(())
772    }
773
774    /// Returns the number of connections in the pool that are busy.
775    ///
776    /// # Examples
777    ///
778    /// ```
779    /// # use oracle::Error;
780    /// # use oracle::pool::PoolBuilder;
781    /// # use oracle::test_util;
782    /// # let username = test_util::main_user();
783    /// # let password = test_util::main_password();
784    /// # let connect_string = test_util::connect_string();
785    /// let pool = PoolBuilder::new(username, password, connect_string)
786    ///     .max_connections(3)
787    ///     .build()?;
788    /// assert_eq!(pool.busy_count()?, 0);
789    /// let conn1 = pool.get()?;
790    /// let conn2 = pool.get()?;
791    /// assert_eq!(pool.busy_count()?, 2);
792    /// conn1.close()?;
793    /// conn2.close()?;
794    /// assert_eq!(pool.busy_count()?, 0);
795    /// # Ok::<(), Error>(())
796    /// ```
797    pub fn busy_count(&self) -> Result<u32> {
798        let mut count = 0;
799        chkerr!(self.ctxt(), dpiPool_getBusyCount(self.handle(), &mut count));
800        Ok(count)
801    }
802
803    /// Returns the mode used for acquiring or getting connections from the pool.
804    ///
805    /// See also [`PoolBuilder::get_mode`] and [`Pool::set_get_mode`].
806    pub fn get_mode(&self) -> Result<GetMode> {
807        let mut val = 0;
808        chkerr!(self.ctxt(), dpiPool_getGetMode(self.handle(), &mut val));
809        match val {
810            DPI_MODE_POOL_GET_WAIT => Ok(GetMode::Wait),
811            DPI_MODE_POOL_GET_NOWAIT => Ok(GetMode::NoWait),
812            DPI_MODE_POOL_GET_FORCEGET => Ok(GetMode::ForceGet),
813            DPI_MODE_POOL_GET_TIMEDWAIT => {
814                let mut val = 0;
815                chkerr!(self.ctxt(), dpiPool_getWaitTimeout(self.handle(), &mut val));
816                Ok(GetMode::TimedWait(Duration::from_millis(val.into())))
817            }
818            _ => Err(Error::internal_error(format!(
819                "unknown dpiPoolGetMode {}",
820                val
821            ))),
822        }
823    }
824
825    /// Sets the mode used for acquiring or getting connections from the pool.
826    ///
827    /// See also [`PoolBuilder::get_mode`] and [`Pool::get_mode`].
828    pub fn set_get_mode(&mut self, mode: &GetMode) -> Result<()> {
829        let get_mode = mode.to_dpi_value();
830        let wait_timeout = mode.to_wait_timeout()?;
831        chkerr!(self.ctxt(), dpiPool_setGetMode(self.handle(), get_mode));
832        if let Some(msecs) = wait_timeout {
833            chkerr!(self.ctxt(), dpiPool_setWaitTimeout(self.handle(), msecs));
834        }
835        Ok(())
836    }
837
838    /// Returns the maximum lifetime a pooled connection may exist.
839    ///
840    /// See also [`PoolBuilder::max_lifetime_connection`] and [`Pool::set_max_lifetime_connection`].
841    pub fn max_lifetime_connection(&self) -> Result<Duration> {
842        let mut val = 0;
843        chkerr!(
844            self.ctxt(),
845            dpiPool_getMaxLifetimeSession(self.handle(), &mut val)
846        );
847        Ok(Duration::from_secs(val.into()))
848    }
849
850    /// Sets the maximum lifetime a pooled connection may exist.
851    ///
852    /// See also [`PoolBuilder::max_lifetime_connection`] and [`Pool::max_lifetime_connection`].
853    pub fn set_max_lifetime_connection(&mut self, dur: Duration) -> Result<()> {
854        let val = U32Seconds::try_from(dur, "max lifetime connection")?;
855        chkerr!(
856            self.ctxt(),
857            dpiPool_setMaxLifetimeSession(self.handle(), val.0)
858        );
859        Ok(())
860    }
861
862    /// Returns the maximum connections per shard. This parameter is used for
863    /// balancing shards.
864    ///
865    /// See also [`PoolBuilder::max_connections_per_shard`] and [`Pool::set_max_connections_per_shard`].
866    pub fn max_connections_per_shard(&self) -> Result<u32> {
867        let mut val = 0;
868        chkerr!(
869            self.ctxt(),
870            dpiPool_getMaxSessionsPerShard(self.handle(), &mut val)
871        );
872        Ok(val)
873    }
874
875    /// Sets the maximum number of connections per shard.
876    ///
877    /// See also [`PoolBuilder::max_connections_per_shard`] and [`Pool::max_connections_per_shard`].
878    pub fn set_max_connections_per_shard(&mut self, max_connections: u32) -> Result<()> {
879        chkerr!(
880            self.ctxt(),
881            dpiPool_setMaxSessionsPerShard(self.handle(), max_connections)
882        );
883        Ok(())
884    }
885
886    /// Returns the number of connections in the pool that are open.
887    pub fn open_count(&self) -> Result<u32> {
888        let mut val = 0;
889        chkerr!(self.ctxt(), dpiPool_getOpenCount(self.handle(), &mut val));
890        Ok(val)
891    }
892
893    /// Returns the ping interval duration, which is used to check the
894    /// healthiness of idle connections before getting checked out. A `None`
895    /// value indicates this check is disabled.
896    ///
897    /// See also [`PoolBuilder::ping_interval`] and [`Pool::set_ping_interval`].
898    pub fn ping_interval(&self) -> Result<Option<Duration>> {
899        let mut val = 0;
900        chkerr!(
901            self.ctxt(),
902            dpiPool_getPingInterval(self.handle(), &mut val)
903        );
904        Ok(I32Seconds(val).into())
905    }
906
907    /// Sets the ping interval duration which is used to to check for
908    /// healthiness of connections. If this time has passed since the last time the
909    /// connection was checked out a ping will be performed. A `None` value will
910    /// disable this check.
911    ///
912    /// See also [`PoolBuilder::ping_interval`] and [`Pool::ping_interval`].
913    pub fn set_ping_interval(&mut self, interval: Option<Duration>) -> Result<()> {
914        let val = I32Seconds::try_from(interval, "ping interval")?;
915        chkerr!(self.ctxt(), dpiPool_setPingInterval(self.handle(), val.0));
916        Ok(())
917    }
918
919    /// Changes pool configuration corresponding to [`PoolBuilder::min_connections`],
920    /// [`PoolBuilder::max_connections`] and [`PoolBuilder::connection_increment`]
921    /// to the specified values.
922    ///
923    /// Connections will be created as needed if the value of `min_connections` is
924    /// increased. Connections will be dropped from the pool as they are released
925    /// back to the pool if `min_connections` is decreased.
926    pub fn reconfigure(
927        &self,
928        min_connections: u32,
929        max_connections: u32,
930        connection_increment: u32,
931    ) -> Result<()> {
932        chkerr!(
933            self.ctxt(),
934            dpiPool_reconfigure(
935                self.handle(),
936                min_connections,
937                max_connections,
938                connection_increment
939            )
940        );
941        Ok(())
942    }
943
944    /// Returns whether or not the SODA metadata cache is enabled or not.
945    ///
946    /// Enabling the SODA metadata cache can significantly improve the
947    /// performance of repeated calls to methods [`soda::Database::create_collection`]
948    /// (when not specifying a value for the metadata parameter) and
949    /// [`soda::Database::open_collection`]. Note that the cache can
950    /// become out of date if changes to the metadata of cached collections
951    /// are made externally.
952    ///
953    /// The SODA metadata cache requires Oracle Client 21.3, or later. It is also
954    /// available in Oracle Client 19 from 19.11.
955    ///
956    /// See also [`Pool::set_soda_metadata_cache`].
957    #[doc(hidden)] // uncomment when SODA is supported.
958    pub fn soda_metadata_cache(&self) -> Result<bool> {
959        let mut val = 0;
960        chkerr!(
961            self.ctxt(),
962            dpiPool_getSodaMetadataCache(self.handle(), &mut val)
963        );
964        Ok(val != 0)
965    }
966
967    /// Sets whether the SODA metadata cache is enabled or not.
968    ///
969    /// The SODA metadata cache requires Oracle Client 21.3, or later. It is also
970    /// available in Oracle Client 19 from 19.11.
971    ///
972    /// See also [`Pool::soda_metadata_cache`].
973    #[doc(hidden)] // uncomment when SODA is supported.
974    pub fn set_soda_metadata_cache(&mut self, enabled: bool) -> Result<()> {
975        let enabled = i32::from(enabled);
976        chkerr!(
977            self.ctxt(),
978            dpiPool_setSodaMetadataCache(self.handle(), enabled)
979        );
980        Ok(())
981    }
982
983    /// Returns the default size of the statement cache for connections in the pool,
984    /// in number of statements.
985    ///
986    /// See also [`PoolBuilder::stmt_cache_size`] and [`Pool::set_stmt_cache_size`].
987    pub fn stmt_cache_size(&self) -> Result<u32> {
988        let mut val = 0;
989        chkerr!(
990            self.ctxt(),
991            dpiPool_getStmtCacheSize(self.handle(), &mut val)
992        );
993        Ok(val)
994    }
995
996    /// Sets the default size of the statement cache for connections in the pool.
997    ///
998    /// See also [`PoolBuilder::stmt_cache_size`] and [`Pool::stmt_cache_size`].
999    pub fn set_stmt_cache_size(&mut self, cache_size: u32) -> Result<()> {
1000        chkerr!(
1001            self.ctxt(),
1002            dpiPool_setStmtCacheSize(self.handle(), cache_size)
1003        );
1004        Ok(())
1005    }
1006
1007    /// Returns the length of time after which idle connections in the
1008    /// pool are terminated. Note that termination only occurs when the pool is
1009    /// accessed. A value of [`Duration::ZERO`] means that no ide connections are terminated.
1010    ///
1011    /// See also [`PoolBuilder::timeout`] and [`Pool::set_timeout`].
1012    pub fn timeout(&self) -> Result<Duration> {
1013        let mut val = 0;
1014        chkerr!(self.ctxt(), dpiPool_getTimeout(self.handle(), &mut val));
1015        Ok(Duration::from_secs(val.into()))
1016    }
1017
1018    /// Sets the amount of time after which idle connections in the
1019    /// pool are terminated. Note that termination only occurs when the pool is
1020    /// accessed. A value of [`Duration::ZERO`] will result in no idle connections being terminated.
1021    ///
1022    /// See also [`PoolBuilder::timeout`] and [`Pool::timeout`].
1023    pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> {
1024        let val = U32Seconds::try_from(timeout, "timeout")?;
1025        chkerr!(self.ctxt(), dpiPool_setTimeout(self.handle(), val.0));
1026        Ok(())
1027    }
1028}
1029
1030impl fmt::Debug for Pool {
1031    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1032        write!(f, "Pool {{ handle: {:?}", self.handle())
1033    }
1034}
1035
1036impl AssertSync for Pool {}
1037impl AssertSend for Pool {}