1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
// Rust-oracle - Rust binding for Oracle database
//
// URL: https://github.com/kubo/rust-oracle
//
//-----------------------------------------------------------------------------
// Copyright (c) 2017-2021 Kubo Takehiro <kubo@jiubao.org>. All rights reserved.
// This program is free software: you can modify it and/or redistribute it
// under the terms of:
//
// (i)  the Universal Permissive License v 1.0 or at your option, any
//      later version (http://oss.oracle.com/licenses/upl); and/or
//
// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
//-----------------------------------------------------------------------------

//! Type definitions for connection pooling
//!
//! This module defines types for connection pooling using [Session Pooling in OCI].
//!
//! [Session Pooling in OCI]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-F9662FFB-EAEF-495C-96FC-49C6D1D9625C
use crate::binding::*;
use crate::chkerr;
use crate::conn::Purity;
use crate::connection::CommonCreateParamsBuilder;
use crate::to_odpi_str;
use crate::AssertSend;
use crate::AssertSync;
use crate::Connection;
use crate::Context;
use crate::DpiPool;
use crate::Error;
use crate::Privilege;
use crate::Result;
use std::convert::TryInto;
use std::fmt;
use std::ptr;
use std::time::Duration;

/// The mode to use when closing pools
///
/// See [`Pool::close`].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CloseMode {
    /// If there are any active connections in the pool an
    /// error is returned.
    Default,

    /// Causes all of the active connections in the pool
    /// to be closed before closing the pool itself.
    Force,
}

impl CloseMode {
    fn to_dpi_value(self) -> dpiPoolCloseMode {
        match self {
            CloseMode::Default => DPI_MODE_POOL_CLOSE_DEFAULT,
            CloseMode::Force => DPI_MODE_POOL_CLOSE_FORCE,
        }
    }
}

/// The mode to use when getting connections from a connection pool
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum GetMode {
    /// The caller should block until a
    /// connection is available from the pool.
    Wait,

    /// The caller should return
    /// immediately, regardless of whether a connection is
    /// available in the pool. If a connection is not
    /// available an error is returned.
    NoWait,

    /// A new connection should be created if
    /// all of the connections in the pool are busy, even if
    /// this exceeds the maximum connections allowable for
    /// the connection pool (see [`PoolBuilder::max_connections`])
    ForceGet,

    /// The caller should block until a
    /// connection is available from the pool, but only for
    /// the specified length of time defined in
    /// the tuple field. If a
    /// connection is not available within the specified
    /// period of time an error is returned.
    TimedWait(Duration),
}

impl GetMode {
    fn to_dpi_value(self) -> dpiPoolGetMode {
        match self {
            GetMode::Wait => DPI_MODE_POOL_GET_WAIT as dpiPoolGetMode,
            GetMode::NoWait => DPI_MODE_POOL_GET_NOWAIT as dpiPoolGetMode,
            GetMode::ForceGet => DPI_MODE_POOL_GET_FORCEGET as dpiPoolGetMode,
            GetMode::TimedWait(_) => DPI_MODE_POOL_GET_TIMEDWAIT as dpiPoolGetMode,
        }
    }

    fn to_wait_timeout(self) -> Result<Option<u32>> {
        if let GetMode::TimedWait(ref dur) = self {
            match dur.as_millis().try_into() {
                Ok(msecs) => Ok(Some(msecs)),
                Err(err) => Err(Error::out_of_range(format!(
                    "too long timed wait duration {:?}",
                    dur
                ))
                .add_source(err)),
            }
        } else {
            Ok(None)
        }
    }
}

/// Whether a connection pool is homogeneous or heterogeneous.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PoolType {
    /// The default pool type.
    /// All connections in the pool are authenticated with the
    /// username and password provided during pool creation.
    Homogeneous,

    /// Connections with different authentication contexts can be
    /// created in the same pool. This pool type also supports
    /// external authentication.
    /// [`PoolBuilder::min_connections`] and [`PoolBuilder::connection_increment`]
    /// are ignored in this type.
    Heterogeneous,
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct I32Seconds(i32);

impl I32Seconds {
    fn try_from(dur: Option<Duration>, msg: &str) -> Result<I32Seconds> {
        if let Some(dur) = dur {
            match dur.as_secs().try_into() {
                Ok(secs) => Ok(I32Seconds(secs)),
                Err(err) => {
                    Err(Error::out_of_range(format!("too long {} {:?}", msg, dur)).add_source(err))
                }
            }
        } else {
            Ok(I32Seconds(-1))
        }
    }

    fn into(self) -> Option<Duration> {
        match self.0.try_into() {
            Ok(secs) => Some(Duration::from_secs(secs)),
            Err(_) => None,
        }
    }
}

/// Additional options to get a connection from a pool
///
/// This is used as the argument of [`Pool::get_with_options`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PoolOptions {
    username: String,
    password: String,
    privilege: Option<Privilege>,
    external_auth: bool,
    tag: String,
    match_any_tag: bool,
    purity: Option<Purity>,
    connection_class: String,
}

impl PoolOptions {
    pub fn new() -> PoolOptions {
        PoolOptions {
            username: "".into(),
            password: "".into(),
            privilege: None,
            external_auth: false,
            tag: "".into(),
            match_any_tag: false,
            purity: None,
            connection_class: "".into(),
        }
    }

    pub fn username<S>(mut self, username: S) -> Self
    where
        S: Into<String>,
    {
        self.username = username.into();
        self
    }

    pub fn password<S>(mut self, password: S) -> Self
    where
        S: Into<String>,
    {
        self.password = password.into();
        self
    }

    pub fn privilege(mut self, privilege: Privilege) -> Self {
        self.privilege = Some(privilege);
        self
    }

    pub fn external_auth(mut self, enable: bool) -> Self {
        self.external_auth = enable;
        self
    }

    pub fn tag<S>(mut self, tag: S) -> Self
    where
        S: Into<String>,
    {
        self.tag = tag.into();
        self
    }

    pub fn match_any_tag(mut self, enable: bool) -> Self {
        self.match_any_tag = enable;
        self
    }

    pub fn purity(mut self, purity: Purity) -> Self {
        self.purity = Some(purity);
        self
    }

    pub fn connection_class<S>(mut self, connection_class: S) -> Self
    where
        S: Into<String>,
    {
        self.connection_class = connection_class.into();
        self
    }

    fn to_dpi_conn_create_params(&self, ctxt: &Context) -> dpiConnCreateParams {
        let mut conn_params = ctxt.conn_create_params();

        if let Some(privilege) = self.privilege {
            conn_params.authMode |= privilege.to_dpi();
        }
        conn_params.externalAuth = i32::from(self.external_auth);
        let s = to_odpi_str(&self.tag);
        conn_params.tag = s.ptr;
        conn_params.tagLength = s.len;
        conn_params.matchAnyTag = i32::from(self.match_any_tag);
        if let Some(purity) = self.purity {
            conn_params.purity = purity.to_dpi();
        }
        let s = to_odpi_str(&self.connection_class);
        conn_params.connectionClass = s.ptr;
        conn_params.connectionClassLength = s.len;
        conn_params
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct U32Seconds(u32);

impl U32Seconds {
    fn try_from(dur: Duration, msg: &str) -> Result<U32Seconds> {
        match dur.as_secs().try_into() {
            Ok(secs) => Ok(U32Seconds(secs)),
            Err(err) => {
                Err(Error::out_of_range(format!("too long {} {:?}", msg, dur)).add_source(err))
            }
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct U32Milliseconds(u32);

impl U32Milliseconds {
    fn try_from(dur: Duration, msg: &str) -> Result<U32Milliseconds> {
        match dur.as_millis().try_into() {
            Ok(secs) => Ok(U32Milliseconds(secs)),
            Err(err) => {
                Err(Error::out_of_range(format!("too long {} {:?}", msg, dur)).add_source(err))
            }
        }
    }
}

/// A bulider to make a connection pool
#[derive(Debug, Clone, PartialEq)]
pub struct PoolBuilder {
    username: String,
    password: String,
    connect_string: String,
    min_connections: Option<u32>,
    max_connections: Option<u32>,
    connection_increment: Option<u32>,
    ping_interval: Option<I32Seconds>,
    ping_timeout: Option<U32Milliseconds>,
    homogeneous: Option<i32>,
    external_auth: Option<bool>,
    get_mode: Option<GetMode>,
    timeout: Option<U32Seconds>,
    max_lifetime_connection: Option<U32Seconds>,
    plsql_fixup_callback: Option<String>,
    max_connections_per_shard: Option<u32>,
    common_params: CommonCreateParamsBuilder,
}

impl PoolBuilder {
    /// Creates a builder to make a connection pool.
    pub fn new<U, P, C>(username: U, password: P, connect_string: C) -> PoolBuilder
    where
        U: Into<String>,
        P: Into<String>,
        C: Into<String>,
    {
        PoolBuilder {
            username: username.into(),
            password: password.into(),
            connect_string: connect_string.into(),
            min_connections: None,
            max_connections: None,
            connection_increment: None,
            ping_interval: None,
            ping_timeout: None,
            homogeneous: None,
            external_auth: None,
            get_mode: None,
            timeout: None,
            max_lifetime_connection: None,
            plsql_fixup_callback: None,
            max_connections_per_shard: None,
            common_params: Default::default(),
        }
    }

    /// Specifies the minimum number of connections to be created by the connection pool.
    /// This value is ignored if the pool type is [`PoolType::Heterogeneous`].
    /// The default value is 1.
    ///
    /// See also [`Pool::reconfigure`].
    pub fn min_connections(&mut self, num: u32) -> &mut PoolBuilder {
        self.min_connections = Some(num);
        self
    }

    /// Specifies the maximum number of connections that can be created by the connection
    /// pool. Values of 1 and higher are acceptable. The default value is 1.
    ///
    /// See also [`Pool::reconfigure`].
    pub fn max_connections(&mut self, num: u32) -> &mut PoolBuilder {
        self.max_connections = Some(num);
        self
    }

    /// Specifies the number of connections that will be created by the connection pool
    /// when more connections are required and the number of connection is less than
    /// the maximum allowed. This value is ignored if the pool type is [`PoolType::Heterogeneous`].
    /// This value added to the [`PoolBuilder::min_connections`] value
    /// must not exceed the [`PoolBuilder::max_connections`].
    /// The default value is 0.
    ///
    /// See also [`Pool::reconfigure`].
    pub fn connection_increment(&mut self, num: u32) -> &mut PoolBuilder {
        self.connection_increment = Some(num);
        self
    }

    /// Specifies the length of time since a connection has last been used
    /// before a ping will be performed to verify that the connection is still
    /// valid. A `None` value disables this check. The default value is 60 seconds.
    ///
    /// See also [`Pool::ping_interval`] and [`Pool::set_ping_interval`].
    pub fn ping_interval(&mut self, dur: Option<Duration>) -> Result<&mut PoolBuilder> {
        self.ping_interval = Some(I32Seconds::try_from(dur, "ping interval")?);
        Ok(self)
    }

    /// Specifies the length of time to wait when performing a ping to
    /// verify the connection is still valid before the connection is considered
    /// invalid and is dropped. The default value is 5 seconds.
    pub fn ping_timeout(&mut self, dur: Duration) -> Result<&mut PoolBuilder> {
        self.ping_timeout = Some(U32Milliseconds::try_from(dur, "ping timeout")?);
        Ok(self)
    }

    /// Specifies whether the pool is homogeneous or heterogeneous. In a homogeneous pool all
    /// connections use the same credentials whereas in a heterogeneous pool other
    /// credentials are permitted. The default value is [`PoolType::Homogeneous`].
    pub fn pool_type(&mut self, pool_type: PoolType) -> &mut PoolBuilder {
        self.homogeneous = Some(match pool_type {
            PoolType::Homogeneous => 1,
            PoolType::Heterogeneous => 0,
        });
        self
    }

    /// Specifies whether external authentication should be used to create the
    /// connections in the pool. If this value is `false`, the user name and password values
    /// must be specified in the call to [`PoolBuilder::new`]; otherwise, the
    /// username and password values must be empty. The default
    /// value is `false`. External authentication cannot be used with homogeneous pools.
    pub fn external_auth(&mut self, b: bool) -> &mut PoolBuilder {
        self.external_auth = Some(b);
        self
    }

    /// Specifies the mode to use when connections are acquired from the pool.
    /// The default value is [`GetMode::NoWait`].
    ///
    /// See also [`Pool::get_mode`] and [`Pool::set_get_mode`].
    pub fn get_mode(&mut self, mode: GetMode) -> &mut PoolBuilder {
        self.get_mode = Some(mode);
        self
    }

    /// Specifies the length of time after which idle connections in the
    /// pool are terminated. Note that termination only occurs when the pool is
    /// accessed. The default value is [`Duration::ZERO`] which means that no idle connections are
    /// terminated.
    ///
    /// See also [`Pool::timeout`] and [`Pool::set_timeout`].
    pub fn timeout(&mut self, dur: Duration) -> Result<&mut PoolBuilder> {
        self.timeout = Some(U32Seconds::try_from(dur, "timeout")?);
        Ok(self)
    }

    /// Specifies the maximum length of time a pooled connection may
    /// exist. Connections in use will not be closed. They become candidates for
    /// termination only when they are released back to the pool and have existed
    /// for longer than max_lifetime_connection. Connection termination only occurs
    /// when the pool is accessed. The default value is [`Duration::ZERO`] which means that there is
    /// no maximum length of time that a pooled connection may exist.
    ///
    /// See also [`Pool::max_lifetime_connection`] and [`Pool::set_max_lifetime_connection`].
    pub fn max_lifetime_connection(&mut self, dur: Duration) -> Result<&mut PoolBuilder> {
        self.max_lifetime_connection = Some(U32Seconds::try_from(dur, "max lifetime connection")?);
        Ok(self)
    }

    /// Specifies the name of a PL/SQL procedure in the format
    /// *schema.package.callback_proc* which will be called when a connection is
    /// checked out from the pool and the requested tag doesn't match the actual
    /// tag assigned to the connection. The procedure accepts the desired
    /// and actual tags as parameters and it is the responsibility of the procedure
    /// to ensure that the connection matches the desired state upon completion. See
    /// the [OCI documentation](https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B853A020-752F-494A-8D88-D0396EF57177)
    /// for more information. This functionality is only available when Oracle Client
    /// is at version 12.2 and higher.
    pub fn plsql_fixup_callback<T>(&mut self, plsql: T) -> &mut PoolBuilder
    where
        T: Into<String>,
    {
        self.plsql_fixup_callback = Some(plsql.into());
        self
    }

    /// Specifies the maximum number of connections that can be created by the connection
    /// pool for each shard in a sharded database. Set this attribute to a value
    /// other than zero to ensure that the pool is balanced towards each shard. A
    /// value of zero will not set any maximum number of connections for each shard.
    /// If the Oracle client library version is less than 18.3, this value is
    /// ignored.
    ///
    /// See also [`Pool::max_connections_per_shard`] and [`Pool::set_max_connections_per_shard`].
    pub fn max_connections_per_shard(&mut self, num: u32) -> &mut PoolBuilder {
        self.max_connections_per_shard = Some(num);
        self
    }

    fn to_dpi_pool_create_params(&self, ctxt: &Context) -> Result<dpiPoolCreateParams> {
        let mut pool_params = ctxt.pool_create_params();

        if let Some(val) = self.min_connections {
            pool_params.minSessions = val;
        }
        if let Some(val) = self.max_connections {
            pool_params.maxSessions = val;
        }
        if let Some(val) = self.connection_increment {
            pool_params.sessionIncrement = val;
        }
        if let Some(val) = self.ping_interval {
            pool_params.pingInterval = val.0;
        }
        if let Some(val) = self.ping_timeout {
            pool_params.pingTimeout = val.0 as i32;
        }
        if let Some(val) = self.homogeneous {
            pool_params.homogeneous = val;
        }
        if let Some(val) = self.external_auth {
            pool_params.externalAuth = i32::from(val);
        }
        if let Some(val) = self.get_mode {
            pool_params.getMode = val.to_dpi_value();
            if let Some(wait_timeout) = val.to_wait_timeout()? {
                pool_params.waitTimeout = wait_timeout;
            }
        }
        if let Some(val) = self.timeout {
            pool_params.timeout = val.0;
        }
        if let Some(val) = self.max_lifetime_connection {
            pool_params.maxLifetimeSession = val.0;
        }
        if let Some(ref val) = self.plsql_fixup_callback {
            let s = to_odpi_str(val);
            pool_params.plsqlFixupCallback = s.ptr;
            pool_params.plsqlFixupCallbackLength = s.len;
        }
        if let Some(val) = self.max_connections_per_shard {
            pool_params.maxSessionsPerShard = val;
        }
        Ok(pool_params)
    }

    /// Reserved for when advanced queuing (AQ) or continuous query
    /// notification (CQN) is supported.
    pub fn events(&mut self, b: bool) -> &mut PoolBuilder {
        self.common_params.events(b);
        self
    }

    /// Specifies edition of [Edition-Based Redefinition][].
    ///
    /// [Edition-Based Redefinition]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-58DE05A0-5DEF-4791-8FA8-F04D11964906
    pub fn edition<S>(&mut self, edition: S) -> &mut PoolBuilder
    where
        S: Into<String>,
    {
        self.common_params.edition(edition);
        self
    }

    /// Sets the driver name displayed in [V$SESSION_CONNECT_INFO.CLIENT_DRIVER][].
    ///
    /// The default value is "rust-oracle : version number". Only the first 8
    /// chracters "rust-ora" are displayed when the Oracle server version is
    /// lower than 12.0.1.2.
    ///
    /// [V$SESSION_CONNECT_INFO.CLIENT_DRIVER]: https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-9F0DCAEA-A67E-4183-89E7-B1555DC591CE
    pub fn driver_name<S>(&mut self, driver_name: S) -> &mut PoolBuilder
    where
        S: Into<String>,
    {
        self.common_params.driver_name(driver_name);
        self
    }

    /// Specifies the number of statements to retain in the statement cache. Use a
    /// value of 0 to disable the statement cache completely.
    /// The default value is 20.
    ///
    /// See also [`Pool::stmt_cache_size`] and [`Pool::set_stmt_cache_size`].
    pub fn stmt_cache_size(&mut self, size: u32) -> &mut PoolBuilder {
        self.common_params.stmt_cache_size(size);
        self
    }

    /// Make a connection pool
    pub fn build(&self) -> Result<Pool> {
        let ctxt = Context::new0()?;
        let username = to_odpi_str(&self.username);
        let password = to_odpi_str(&self.password);
        let connect_string = to_odpi_str(&self.connect_string);
        let common_params = self.common_params.build(&ctxt);
        let mut pool_params = self.to_dpi_pool_create_params(&ctxt)?;
        let mut handle = ptr::null_mut();
        chkerr!(
            &ctxt,
            dpiPool_create(
                ctxt.context,
                username.ptr,
                username.len,
                password.ptr,
                password.len,
                connect_string.ptr,
                connect_string.len,
                &common_params,
                &mut pool_params,
                &mut handle
            )
        );
        Ok(Pool {
            ctxt,
            handle: DpiPool::new(handle),
        })
    }
}

/// Connection pool
///
///
/// # Examples
///
/// Get connections in a pool
///
/// ```
/// # use oracle::Error;
/// # use oracle::pool::PoolBuilder;
/// # use oracle::test_util;
/// # let username = test_util::main_user();
/// # let password = test_util::main_password();
/// # let connect_string = test_util::connect_string();
/// // Create a pool
/// let pool = PoolBuilder::new(username, password, connect_string)
///     .max_connections(20)
///     .build()?;
///
/// // Get connections from the pool.
/// let conn1 = pool.get()?;
/// let conn2 = pool.get()?;
///
/// assert_eq!(pool.open_count()?, 2); // Two connections are in the pool.
/// assert_eq!(pool.busy_count()?, 2); // Two connections are in use.
///
/// // Return the connections to the pool.
/// conn1.close()?;
/// conn2.close()?;
///
/// assert_eq!(pool.open_count()?, 2); // Two connections are in the pool.
/// assert_eq!(pool.busy_count()?, 0); // No connections are in use.
/// # Ok::<(), Error>(())
/// ```
///
/// Use a [heterogeneous pool](PoolType::Heterogeneous) to pool different users' connections
///
/// ```
/// # use oracle::Error;
/// # use oracle::Connector;
/// # use oracle::pool::{PoolBuilder, PoolOptions, PoolType};
/// # use oracle::test_util;
/// # let username = test_util::main_user();
/// # let username = username.as_ref();
/// # let password = test_util::main_password();
/// # let another_username = test_util::edition_user();
/// # let another_username = another_username.as_ref();
/// # let another_password = test_util::edition_password();
/// # let connect_string = test_util::connect_string();
/// // Create a pool
/// let pool = PoolBuilder::new(username, password, connect_string)
///     .pool_type(PoolType::Heterogeneous)
///     .max_connections(20)
///     .build()?;
///
/// // Get a connection from the pool.
/// let conn1 = pool.get()?;
/// let conn1_user = conn1.query_row_as::<String>("select lower(user) from dual", &[])?;
/// assert_eq!(conn1_user, username);
///
/// // Get an another user's connection.
/// let opts = PoolOptions::new().username(another_username).password(another_password);
/// let conn2 = pool.get_with_options(&opts)?; // with connector to pass username and password
/// let conn2_user = conn2.query_row_as::<String>("select lower(user) from dual", &[])?;
/// assert_eq!(conn2_user, another_username);
/// # Ok::<(), Error>(())
/// ```
///
/// Get connections with tags (`NAME=VALUE` pairs)
///
/// ```
/// # use oracle::Error;
/// # use oracle::Connector;
/// # use oracle::conn;
/// # use oracle::pool::{PoolBuilder, PoolOptions};
/// # use oracle::test_util;
/// # let username = test_util::main_user();
/// # let password = test_util::main_password();
/// # let connect_string = test_util::connect_string();
/// // Create a pool
/// let pool = PoolBuilder::new(username, password, connect_string)
///     .max_connections(20)
///     .build()?;
///
/// // Create Pool options to specify a tag.
/// let opts = PoolOptions::new().tag("LANG=FRENCH");
///
/// // Get a connection with tag "LANG=FRENCH".
/// // There are no connections with the tag at this point.
/// let conn = pool.get_with_options(&opts)?;
/// assert_eq!(conn.tag_found(), false, "conn.tag_found() (1)"); // new connection
/// // Change the nls_language for later.
/// if !conn.tag_found() {
///   conn.execute("alter session set nls_language = FRENCH", &[])?;
/// }
/// // ...
/// // use the connection
/// // ...
/// // return it to the pool with new tag.
/// conn.close_with_mode(conn::CloseMode::Retag("LANG=FRENCH"))?;
///
/// // Get a connection with tag "LANG=FRENCH" again.
/// // There is one connection with the tag at this point.
/// let conn = pool.get_with_options(&opts)?;
/// assert_eq!(conn.tag_found(), true, "conn.tag_found() (2)");
/// assert_eq!(conn.tag(), "LANG=FRENCH", "conn.tag() (2)");
/// // Check whether this is the connection previously
/// let sql = "select value from nls_session_parameters where parameter = 'NLS_LANGUAGE'";
/// assert_eq!(conn.query_row_as::<String>(sql, &[])?, "FRENCH");
/// // ...
/// // The connection has been tagged already. There is no need to set a new tag.
/// conn.close()?;
///
/// # Ok::<(), Error>(())
/// ```
#[derive(Clone)]
pub struct Pool {
    ctxt: Context,
    handle: DpiPool,
}

impl Pool {
    fn handle(&self) -> *mut dpiPool {
        self.handle.raw()
    }

    fn ctxt(&self) -> &Context {
        &self.ctxt
    }

    /// Gets a connection from the pool with default parameters.
    ///
    /// Use [`Pool::get_with_options`] to get a new one with
    /// additional parameters.
    ///
    /// When the connection is dropped, it backs to the pool
    /// for subsequent calls to this function. The connection
    /// can be returned back to the pool earlier by calling
    /// [`Connection::close`].
    pub fn get(&self) -> Result<Connection> {
        self.get_with_options(&PoolOptions::new())
    }

    /// Acquires a connection from the specified connection pool.
    ///
    /// See also [`Pool::get`].
    pub fn get_with_options(&self, options: &PoolOptions) -> Result<Connection> {
        let ctxt = Context::new()?;
        let username = to_odpi_str(&options.username);
        let password = to_odpi_str(&options.password);
        let mut conn_params = options.to_dpi_conn_create_params(&ctxt);
        let mut handle = ptr::null_mut();
        chkerr!(
            &ctxt,
            dpiPool_acquireConnection(
                self.handle(),
                username.ptr,
                username.len,
                password.ptr,
                password.len,
                &mut conn_params,
                &mut handle
            )
        );
        ctxt.set_warning();
        Ok(Connection::from_dpi_handle(ctxt, handle, &conn_params))
    }

    /// Closes the pool and makes it unusable for further activity.
    pub fn close(&self, mode: &CloseMode) -> Result<()> {
        chkerr!(
            self.ctxt(),
            dpiPool_close(self.handle(), mode.to_dpi_value())
        );
        Ok(())
    }

    /// Returns the number of connections in the pool that are busy.
    ///
    /// # Examples
    ///
    /// ```
    /// # use oracle::Error;
    /// # use oracle::pool::PoolBuilder;
    /// # use oracle::test_util;
    /// # let username = test_util::main_user();
    /// # let password = test_util::main_password();
    /// # let connect_string = test_util::connect_string();
    /// let pool = PoolBuilder::new(username, password, connect_string)
    ///     .max_connections(3)
    ///     .build()?;
    /// assert_eq!(pool.busy_count()?, 0);
    /// let conn1 = pool.get()?;
    /// let conn2 = pool.get()?;
    /// assert_eq!(pool.busy_count()?, 2);
    /// conn1.close()?;
    /// conn2.close()?;
    /// assert_eq!(pool.busy_count()?, 0);
    /// # Ok::<(), Error>(())
    /// ```
    pub fn busy_count(&self) -> Result<u32> {
        let mut count = 0;
        chkerr!(self.ctxt(), dpiPool_getBusyCount(self.handle(), &mut count));
        Ok(count)
    }

    /// Returns the mode used for acquiring or getting connections from the pool.
    ///
    /// See also [`PoolBuilder::get_mode`] and [`Pool::set_get_mode`].
    pub fn get_mode(&self) -> Result<GetMode> {
        let mut val = 0;
        chkerr!(self.ctxt(), dpiPool_getGetMode(self.handle(), &mut val));
        match val as u32 {
            DPI_MODE_POOL_GET_WAIT => Ok(GetMode::Wait),
            DPI_MODE_POOL_GET_NOWAIT => Ok(GetMode::NoWait),
            DPI_MODE_POOL_GET_FORCEGET => Ok(GetMode::ForceGet),
            DPI_MODE_POOL_GET_TIMEDWAIT => {
                let mut val = 0;
                chkerr!(self.ctxt(), dpiPool_getWaitTimeout(self.handle(), &mut val));
                Ok(GetMode::TimedWait(Duration::from_millis(val.into())))
            }
            _ => Err(Error::internal_error(format!(
                "unknown dpiPoolGetMode {}",
                val
            ))),
        }
    }

    /// Sets the mode used for acquiring or getting connections from the pool.
    ///
    /// See also [`PoolBuilder::get_mode`] and [`Pool::get_mode`].
    pub fn set_get_mode(&mut self, mode: &GetMode) -> Result<()> {
        let get_mode = mode.to_dpi_value();
        let wait_timeout = mode.to_wait_timeout()?;
        chkerr!(self.ctxt(), dpiPool_setGetMode(self.handle(), get_mode));
        if let Some(msecs) = wait_timeout {
            chkerr!(self.ctxt(), dpiPool_setWaitTimeout(self.handle(), msecs));
        }
        Ok(())
    }

    /// Returns the maximum lifetime a pooled connection may exist.
    ///
    /// See also [`PoolBuilder::max_lifetime_connection`] and [`Pool::set_max_lifetime_connection`].
    pub fn max_lifetime_connection(&self) -> Result<Duration> {
        let mut val = 0;
        chkerr!(
            self.ctxt(),
            dpiPool_getMaxLifetimeSession(self.handle(), &mut val)
        );
        Ok(Duration::from_secs(val.into()))
    }

    /// Sets the maximum lifetime a pooled connection may exist.
    ///
    /// See also [`PoolBuilder::max_lifetime_connection`] and [`Pool::max_lifetime_connection`].
    pub fn set_max_lifetime_connection(&mut self, dur: Duration) -> Result<()> {
        let val = U32Seconds::try_from(dur, "max lifetime connection")?;
        chkerr!(
            self.ctxt(),
            dpiPool_setMaxLifetimeSession(self.handle(), val.0)
        );
        Ok(())
    }

    /// Returns the maximum connections per shard. This parameter is used for
    /// balancing shards.
    ///
    /// See also [`PoolBuilder::max_connections_per_shard`] and [`Pool::set_max_connections_per_shard`].
    pub fn max_connections_per_shard(&self) -> Result<u32> {
        let mut val = 0;
        chkerr!(
            self.ctxt(),
            dpiPool_getMaxSessionsPerShard(self.handle(), &mut val)
        );
        Ok(val)
    }

    /// Sets the maximum number of connections per shard.
    ///
    /// See also [`PoolBuilder::max_connections_per_shard`] and [`Pool::max_connections_per_shard`].
    pub fn set_max_connections_per_shard(&mut self, max_connections: u32) -> Result<()> {
        chkerr!(
            self.ctxt(),
            dpiPool_setMaxSessionsPerShard(self.handle(), max_connections)
        );
        Ok(())
    }

    /// Returns the number of connections in the pool that are open.
    pub fn open_count(&self) -> Result<u32> {
        let mut val = 0;
        chkerr!(self.ctxt(), dpiPool_getOpenCount(self.handle(), &mut val));
        Ok(val)
    }

    /// Returns the ping interval duration, which is used to check the
    /// healthiness of idle connections before getting checked out. A `None`
    /// value indicates this check is disabled.
    ///
    /// See also [`PoolBuilder::ping_interval`] and [`Pool::set_ping_interval`].
    pub fn ping_interval(&self) -> Result<Option<Duration>> {
        let mut val = 0;
        chkerr!(
            self.ctxt(),
            dpiPool_getPingInterval(self.handle(), &mut val)
        );
        Ok(I32Seconds(val).into())
    }

    /// Sets the ping interval duration which is used to to check for
    /// healthiness of connections. If this time has passed since the last time the
    /// connection was checked out a ping will be performed. A `None` value will
    /// disable this check.
    ///
    /// See also [`PoolBuilder::ping_interval`] and [`Pool::ping_interval`].
    pub fn set_ping_interval(&mut self, interval: Option<Duration>) -> Result<()> {
        let val = I32Seconds::try_from(interval, "ping interval")?;
        chkerr!(self.ctxt(), dpiPool_setPingInterval(self.handle(), val.0));
        Ok(())
    }

    /// Changes pool configuration corresponding to [`PoolBuilder::min_connections`],
    /// [`PoolBuilder::max_connections`] and [`PoolBuilder::connection_increment`]
    /// to the specified values.
    ///
    /// Connections will be created as needed if the value of `min_connections` is
    /// increased. Connections will be dropped from the pool as they are released
    /// back to the pool if `min_connections` is decreased.
    pub fn reconfigure(
        &self,
        min_connections: u32,
        max_connections: u32,
        connection_increment: u32,
    ) -> Result<()> {
        chkerr!(
            self.ctxt(),
            dpiPool_reconfigure(
                self.handle(),
                min_connections,
                max_connections,
                connection_increment
            )
        );
        Ok(())
    }

    /// Returns whether or not the SODA metadata cache is enabled or not.
    ///
    /// Enabling the SODA metadata cache can significantly improve the
    /// performance of repeated calls to methods [`soda::Database::create_collection`]
    /// (when not specifying a value for the metadata parameter) and
    /// [`soda::Database::open_collection`]. Note that the cache can
    /// become out of date if changes to the metadata of cached collections
    /// are made externally.
    ///
    /// The SODA metadata cache requires Oracle Client 21.3, or later. It is also
    /// available in Oracle Client 19 from 19.11.
    ///
    /// See also [`Pool::set_soda_metadata_cache`].
    #[doc(hidden)] // uncomment when SODA is supported.
    pub fn soda_metadata_cache(&self) -> Result<bool> {
        let mut val = 0;
        chkerr!(
            self.ctxt(),
            dpiPool_getSodaMetadataCache(self.handle(), &mut val)
        );
        Ok(val != 0)
    }

    /// Sets whether the SODA metadata cache is enabled or not.
    ///
    /// The SODA metadata cache requires Oracle Client 21.3, or later. It is also
    /// available in Oracle Client 19 from 19.11.
    ///
    /// See also [`Pool::soda_metadata_cache`].
    #[doc(hidden)] // uncomment when SODA is supported.
    pub fn set_soda_metadata_cache(&mut self, enabled: bool) -> Result<()> {
        let enabled = i32::from(enabled);
        chkerr!(
            self.ctxt(),
            dpiPool_setSodaMetadataCache(self.handle(), enabled)
        );
        Ok(())
    }

    /// Returns the default size of the statement cache for connections in the pool,
    /// in number of statements.
    ///
    /// See also [`PoolBuilder::stmt_cache_size`] and [`Pool::set_stmt_cache_size`].
    pub fn stmt_cache_size(&self) -> Result<u32> {
        let mut val = 0;
        chkerr!(
            self.ctxt(),
            dpiPool_getStmtCacheSize(self.handle(), &mut val)
        );
        Ok(val)
    }

    /// Sets the default size of the statement cache for connections in the pool.
    ///
    /// See also [`PoolBuilder::stmt_cache_size`] and [`Pool::stmt_cache_size`].
    pub fn set_stmt_cache_size(&mut self, cache_size: u32) -> Result<()> {
        chkerr!(
            self.ctxt(),
            dpiPool_setStmtCacheSize(self.handle(), cache_size)
        );
        Ok(())
    }

    /// Returns the length of time after which idle connections in the
    /// pool are terminated. Note that termination only occurs when the pool is
    /// accessed. A value of [`Duration::ZERO`] means that no ide connections are terminated.
    ///
    /// See also [`PoolBuilder::timeout`] and [`Pool::set_timeout`].
    pub fn timeout(&self) -> Result<Duration> {
        let mut val = 0;
        chkerr!(self.ctxt(), dpiPool_getTimeout(self.handle(), &mut val));
        Ok(Duration::from_secs(val.into()))
    }

    /// Sets the amount of time after which idle connections in the
    /// pool are terminated. Note that termination only occurs when the pool is
    /// accessed. A value of [`Duration::ZERO`] will result in no idle connections being terminated.
    ///
    /// See also [`PoolBuilder::timeout`] and [`Pool::timeout`].
    pub fn set_timeout(&mut self, timeout: Duration) -> Result<()> {
        let val = U32Seconds::try_from(timeout, "timeout")?;
        chkerr!(self.ctxt(), dpiPool_setTimeout(self.handle(), val.0));
        Ok(())
    }
}

impl fmt::Debug for Pool {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Poll {{ handle: {:?}", self.handle())
    }
}

impl AssertSync for Pool {}
impl AssertSend for Pool {}