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 {}