sibyl/pool/session/
nonblocking.rs

1//! Session pool nonblocking mode implementation
2
3use super::{SessionPool, SPool};
4use crate::{Session, Result, oci::{self, *}, Environment, task};
5use std::{ptr, slice, str, marker::PhantomData, sync::Arc};
6
7impl SPool {
8    pub(crate) async fn new(env: &Environment, dblink: &str, username: &str, password: &str, min: usize, inc: usize, max: usize, session_state_fixup_callback: &str) -> Result<Self> {
9        let err  = Handle::<OCIError>::new(&env)?;
10        let pool = Handle::<OCISPool>::new(&env)?;
11        let info = Handle::<OCIAuthInfo>::new(&env)?;
12        info.set_attr(OCI_ATTR_DRIVER_NAME, "sibyl", &err)?;
13        if session_state_fixup_callback.len() > 0 {
14            info.set_attr(OCI_ATTR_FIXUP_CALLBACK, session_state_fixup_callback, &err)?;
15        }
16        pool.set_attr(OCI_ATTR_SPOOL_AUTH, info.get_ptr(), &err)?;
17
18        let mut spool = Self { pool, info, err, env: env.get_env(), name: Vec::new() };
19        let dblink = String::from(dblink);
20        let username = String::from(username);
21        let password = String::from(password);
22
23        task::execute_blocking(move || -> Result<Self> {
24            let mut pool_name_ptr = ptr::null::<u8>();
25            let mut pool_name_len = 0u32;
26            oci::session_pool_create(
27                spool.env.as_ref(), spool.err.as_ref(), spool.pool.as_ref(),
28                &mut pool_name_ptr, &mut pool_name_len,
29                dblink.as_ptr(), dblink.len() as _,
30                min as _, max as _, inc as _,
31                username.as_ptr(), username.len() as _,
32                password.as_ptr(), password.len() as _,
33                OCI_SPC_HOMOGENEOUS | OCI_SPC_STMTCACHE
34            )?;
35            let name = unsafe {
36                slice::from_raw_parts(pool_name_ptr, pool_name_len as usize)
37            };
38            spool.name.extend_from_slice(name);
39            Ok(spool)
40        }).await?
41    }
42}
43
44impl<'a> SessionPool<'a> {
45    pub(crate) async fn new(env: &'a Environment, dblink: &str, username: &str, password: &str, min: usize, inc: usize, max: usize, session_state_fixup_callback: &str) -> Result<SessionPool<'a>> {
46        let inner = SPool::new(env, dblink, username, password, min, inc, max, session_state_fixup_callback).await?;
47        let inner = Arc::new(inner);
48        Ok(Self { inner, phantom_env: PhantomData })
49    }
50
51    /**
52        Returns a default (untagged) session from this session pool.
53
54        # Example
55
56        ```
57        use sibyl::{Environment, Session, Date, Result};
58        use once_cell::sync::OnceCell;
59        use std::{env, sync::Arc};
60
61        fn main() -> Result<()> {
62            // `sibyl::block_on` is used to run Sibyl's async doc tests
63            // and would not be used when `main` can be declared as async.
64            sibyl::block_on(async {
65                static ORACLE : OnceCell<Environment> = OnceCell::new();
66                let oracle = ORACLE.get_or_try_init(|| {
67                    Environment::new()
68                })?;
69
70                let dbname = env::var("DBNAME").expect("database name");
71                let dbuser = env::var("DBUSER").expect("user name");
72                let dbpass = env::var("DBPASS").expect("password");
73
74                let pool = oracle.create_session_pool(&dbname, &dbuser, &dbpass, 0, 1, 4).await?;
75                let pool = Arc::new(pool);
76
77                let mut workers = Vec::with_capacity(10);
78                for _i in 0..workers.capacity() {
79                    let pool = pool.clone();
80                    // `sibyl::spawn` abstracts `spawn` for Sibyl's use with different async
81                    // runtimes. One does not need it when a specific runtime is selected.
82                    let handle = sibyl::spawn(async move {
83                        let session = pool.get_session().await?;
84
85                        select_latest_hire(&session).await
86                    });
87                    workers.push(handle);
88                }
89                for handle in workers {
90                    let worker_result = handle.await;
91                    #[cfg(any(feature="tokio", feature="actix"))]
92                    let worker_result = worker_result.expect("completed task result");
93
94                    let name = worker_result?;
95                    assert_eq!(name, "Amit Banda was hired on April 21, 2008");
96                }
97                Ok(())
98            })
99        }
100
101        async fn select_latest_hire(session: &Session<'_>) -> Result<String> {
102            let stmt = session.prepare("
103                SELECT first_name, last_name, hire_date
104                  FROM (
105                        SELECT first_name, last_name, hire_date
106                             , Row_Number() OVER (ORDER BY hire_date DESC, last_name) AS hire_date_rank
107                          FROM hr.employees
108                       )
109                 WHERE hire_date_rank = 1
110            ").await?;
111            if let Some( row ) = stmt.query_single(()).await? {
112                let first_name : Option<&str> = row.get(0)?;
113                let last_name : &str = row.get(1)?;
114                let name = first_name.map_or(last_name.to_string(), |first_name| format!("{} {}", first_name, last_name));
115                let hire_date : Date = row.get(2)?;
116                let hire_date = hire_date.to_string("FMMonth DD, YYYY")?;
117                Ok(format!("{} was hired on {}", name, hire_date))
118            } else {
119                Ok("Not found".to_string())
120            }
121        }
122        ```
123    */
124    pub async fn get_session(&self) -> Result<Session<'_>> {
125        let (session, _found) = Session::from_session_pool(self, "").await?;
126        Ok(session)
127    }
128
129    /**
130    Returns a tagged session, i.e. a session with the specified type/tag.
131
132    The tags provide a way to customize sessions in the pool. A client can get a default or untagged
133    session from a pool, set certain attributes on the session (such as globalization settings) and
134    label it with an appropriate tag.
135
136    This session or a session with the same attributes can then be requested by providing the same
137    tag that was used during session customization.
138
139    If a user asks for a session with tag 'A', and a matching session is not available, an appropriately
140    authenticated untagged session is returned, if such a session is free.
141
142    # Parameters
143
144    - `taginfo` - Either a single or a [Multi-Property Tag](https://docs.oracle.com/en/database/oracle/oracle-database/19/lnoci/session-and-connection-pooling.html#GUID-DFA21225-E83C-4177-A79A-B8BA29DC662C).
145    
146    # Returns
147
148    A tuple with the returned session and the boolean flag. The flag indicates whether the type of the returned
149    session is the same as requested.
150
151    # Example
152
153    ```
154    # fn main() -> sibyl::Result<()> {
155    # sibyl::block_on(async {
156    # use once_cell::sync::OnceCell;
157    # static ORACLE: OnceCell<sibyl::Environment> = OnceCell::new();
158    # let oracle = ORACLE.get_or_try_init(|| sibyl::Environment::new())?;
159    # let dbname = std::env::var("DBNAME").expect("database name");
160    # let dbuser = std::env::var("DBUSER").expect("user name");
161    # let dbpass = std::env::var("DBPASS").expect("password");
162    let pool = oracle.create_session_pool(&dbname, &dbuser, &dbpass, 0, 1, 5).await?;
163
164    {
165        let (session,found) = pool.get_tagged_session("CUSTOM").await?;
166        assert!(!found, "a default (not yet tagged) session was returned");
167        if !found {
168    #        let stmt = session.prepare("SELECT value FROM nls_session_parameters WHERE parameter=:PARAM_NAME").await?;
169    #        let row = stmt.query_single("NLS_DATE_FORMAT").await?.expect("one row");
170    #        let fmt: &str = row.get(0)?;
171    #        assert_ne!(fmt, "YYYY-MM-DD", "Default NLS_DATE_FORMAT differs from what we want to set");
172            let stmt = session.prepare("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD'").await?;
173            stmt.execute(()).await?;
174            session.set_tag("CUSTOM")?;
175        }
176    } // session is released back to the pool here
177
178    {
179        let (session,found) = pool.get_tagged_session("CUSTOM").await?;
180        assert!(found, "the customized session was returned");
181
182        let stmt = session.prepare("SELECT value FROM nls_session_parameters WHERE parameter=:PARAM_NAME").await?;
183        let row = stmt.query_single("NLS_DATE_FORMAT").await?.expect("one row");
184        let fmt: &str = row.get(0)?;
185        assert_eq!(fmt, "YYYY-MM-DD", "Session uses custom NLS_DATE_FORMAT");
186    }
187    # Ok(()) })
188    # }
189    ```
190    */
191    pub async fn get_tagged_session(&self, taginfo: &str) -> Result<(Session<'_>,bool)> {
192        Session::from_session_pool(self, taginfo).await
193    }
194}