Skip to main content

sibyl/
stmt.rs

1//! SQL or PL/SQL statement
2
3pub(crate) mod args;
4mod bind;
5mod cols;
6mod cursor;
7mod rows;
8mod data;
9
10#[cfg(feature="blocking")]
11#[cfg_attr(docsrs, doc(cfg(feature="blocking")))]
12mod blocking;
13
14#[cfg(feature="nonblocking")]
15#[cfg_attr(docsrs, doc(cfg(feature="nonblocking")))]
16mod nonblocking;
17
18pub use args::ToSql;
19pub use data::FromSql;
20pub use bind::Params;
21pub use cursor::Cursor;
22pub use rows::{Row, Rows};
23pub use cols::ColumnType;
24
25use once_cell::sync::OnceCell;
26use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
27
28use crate::{Result, session::SvcCtx, oci::*, Session, types::Ctx};
29
30use std::{sync::Arc, fmt::Display};
31
32use cols::{Columns, ColumnInfo};
33
34/// Allows column or output variable identification by either
35/// its numeric position or its name.
36pub trait Position: Display {
37    fn index(&self) -> Option<usize>;
38    fn name(&self)  -> Option<&str>  { None }
39}
40
41impl Position for usize {
42    fn index(&self) -> Option<usize> { Some(*self) }
43}
44
45impl Position for &str {
46    fn index(&self) -> Option<usize> { None }
47    fn name(&self)  -> Option<&str>  { Some(*self) }
48}
49
50/// Represents a prepared for execution SQL or PL/SQL statement
51pub struct Statement<'a> {
52    session:  &'a Session<'a>,
53    stmt:     Ptr<OCIStmt>,
54    params:   Option<RwLock<Params>>,
55    cols:     OnceCell<RwLock<Columns>>,
56    err:      Handle<OCIError>,
57    svc:      Arc<SvcCtx>,
58    max_long: u32,
59}
60
61#[cfg(not(docsrs))]
62impl Drop for Statement<'_> {
63    fn drop(&mut self) {
64        let _ = self.svc;
65        
66        #[cfg(feature="nonblocking")]
67        let _ = self.svc.set_blocking_mode();
68
69        oci_stmt_release(&self.stmt, &self.err);
70
71        #[cfg(feature="nonblocking")]
72        let _ = self.svc.set_nonblocking_mode();
73    }
74}
75
76impl AsRef<OCIEnv> for Statement<'_> {
77    fn as_ref(&self) -> &OCIEnv {
78        self.session.as_ref()
79    }
80}
81
82impl AsRef<OCIError> for Statement<'_> {
83    fn as_ref(&self) -> &OCIError {
84        self.session.as_ref()
85    }
86}
87
88impl AsRef<OCISvcCtx> for Statement<'_> {
89    fn as_ref(&self) -> &OCISvcCtx {
90        self.session.as_ref()
91    }
92}
93
94impl AsRef<OCIStmt> for Statement<'_> {
95    fn as_ref(&self) -> &OCIStmt {
96        self.stmt.as_ref()
97    }
98}
99
100impl Ctx for Statement<'_> {
101    fn try_as_session(&self) -> Option<&OCISession> {
102        self.session.try_as_session()
103    }
104}
105
106impl<'a> Statement<'a> {
107    fn get_attr<T: attr::AttrGet>(&self, attr_type: u32) -> Result<T> {
108        attr::get(attr_type, OCI_HTYPE_STMT, self.stmt.as_ref(), self.as_ref())
109    }
110
111    fn set_attr<T: attr::AttrSet>(&self, attr_type: u32, attr_val: T) -> Result<()> {
112        attr::set(attr_type, attr_val, OCI_HTYPE_STMT, self.stmt.as_ref(), self.as_ref())
113    }
114
115    pub(crate) fn read_columns(&self) -> RwLockReadGuard<'_,Columns> {
116        self.cols.get().expect("locked columns").read()
117    }
118
119    pub(crate) fn write_columns(&self) -> RwLockWriteGuard<'_,Columns> {
120        self.cols.get().expect("locked columns").write()
121    }
122
123    pub(crate) fn session(&self) -> &Session<'_> {
124        self.session
125    }
126
127    /**
128    Sets the number of top-level rows to be prefetched. The default value is 10 rows.
129
130    # Parameters
131
132    * `num_rows` The number of top-level rows to be prefetched
133
134    # Example
135
136    ## Blocking
137
138    ```
139    # #[cfg(feature="blocking")]
140    # fn main() -> sibyl::Result<()> {
141    # let session = sibyl::test_env::get_session()?;
142    let stmt = session.prepare("
143        SELECT employee_id, first_name, last_name
144          FROM hr.employees
145         WHERE manager_id = :id
146    ")?;
147    stmt.set_prefetch_rows(5)?;
148    # Ok(())
149    # }
150    # #[cfg(feature="nonblocking")]
151    # fn main() {}
152    ```
153
154    ## Nonblocking
155
156    ```
157    # #[cfg(feature="nonblocking")]
158    # fn main() -> sibyl::Result<()> {
159    # sibyl::block_on(async {
160    # let session = sibyl::test_env::get_session().await?;
161    let stmt = session.prepare("
162        SELECT employee_id, first_name, last_name
163          FROM hr.employees
164         WHERE manager_id = :id
165    ").await?;
166    stmt.set_prefetch_rows(5)?;
167    # Ok(()) })
168    # }
169    # #[cfg(feature="blocking")]
170    # fn main() {}
171    ```
172    */
173    pub fn set_prefetch_rows(&self, num_rows: u32) -> Result<()> {
174        self.set_attr(OCI_ATTR_PREFETCH_ROWS, num_rows)
175    }
176
177    /**
178    Sets the maximum size of data that will be fetched from LONG and LONG RAW.
179
180    By default 32768 bytes are allocated for values from LONG and LONG RAW columns.
181    If the actual value is expected to be larger than that, then the "max long size"
182    has to be set **before** the `query` is run.
183
184    # Parameters
185
186    * `size` - The maximum sizeof data that will be fetched
187
188    # Example
189
190    ## Blocking
191
192    ```
193    /*
194        CREATE TABLE long_and_raw_test_data (
195            id      INTEGER GENERATED ALWAYS AS IDENTITY,
196            bin     RAW(100),
197            text    LONG
198        )
199     */
200    static TEXT : &str = "
201        When I have fears that I may cease to be
202        Before my pen has gleaned my teeming brain,
203        Before high-pilèd books, in charactery,
204        Hold like rich garners the full ripened grain;
205        When I behold, upon the night’s starred face,
206        Huge cloudy symbols of a high romance,
207        And think that I may never live to trace
208        Their shadows with the magic hand of chance;
209        And when I feel, fair creature of an hour,
210        That I shall never look upon thee more,
211        Never have relish in the faery power
212        Of unreflecting love—then on the shore
213        Of the wide world I stand alone, and think
214        Till love and fame to nothingness do sink.
215    ";
216    # #[cfg(feature="blocking")]
217    # fn main() -> sibyl::Result<()> {
218    # let session = sibyl::test_env::get_session()?;
219    # let stmt = session.prepare("
220    #     INSERT INTO long_and_raw_test_data (text) VALUES (:TEXT) RETURNING id INTO :ID
221    # ")?;
222    # let mut id = 0;
223    # let count = stmt.execute(((":TEXT", &TEXT), (":ID", &mut id)))?;
224    let mut stmt = session.prepare("
225        SELECT text
226          FROM long_and_raw_test_data
227         WHERE id = :ID
228    ")?;
229    stmt.set_max_long_size(100_000);
230    let row = stmt.query_single(&id)?.unwrap();
231    let txt : &str = row.get(0)?;
232    assert_eq!(txt, TEXT);
233    # Ok(())
234    # }
235    # #[cfg(feature="nonblocking")]
236    # fn main() {}
237    ```
238
239    ## Nonblocking
240
241    ```
242    static TEXT : &str = "
243        When I have fears that I may cease to be
244        Before my pen has gleaned my teeming brain,
245        Before high-pilèd books, in charactery,
246        Hold like rich garners the full ripened grain;
247        When I behold, upon the night’s starred face,
248        Huge cloudy symbols of a high romance,
249        And think that I may never live to trace
250        Their shadows with the magic hand of chance;
251        And when I feel, fair creature of an hour,
252        That I shall never look upon thee more,
253        Never have relish in the faery power
254        Of unreflecting love—then on the shore
255        Of the wide world I stand alone, and think
256        Till love and fame to nothingness do sink.
257    ";
258    # #[cfg(feature="nonblocking")]
259    # fn main() -> sibyl::Result<()> {
260    # sibyl::block_on(async {
261    # let session = sibyl::test_env::get_session().await?;
262    # let stmt = session.prepare("
263    #     INSERT INTO long_and_raw_test_data (text) VALUES (:TEXT) RETURNING id INTO :ID
264    # ").await?;
265    # let mut id = 0;
266    # let count = stmt.execute(((":TEXT", &TEXT), (":ID", &mut id))).await?;
267    let mut stmt = session.prepare("
268        SELECT text
269          FROM long_and_raw_test_data
270         WHERE id = :ID
271    ").await?;
272    stmt.set_max_long_size(100_000);
273    let row = stmt.query_single(&id).await?.unwrap();
274    let txt : &str = row.get(0)?;
275    assert_eq!(txt, TEXT);
276    # Ok(()) })
277    # }
278    # #[cfg(feature="blocking")]
279    # fn main() {}
280    ```
281    */
282    pub fn set_max_long_size(&mut self, size: u32) {
283        self.max_long = size;
284    }
285
286    /**
287    Returns he number of columns in the select-list of this statement.
288
289    # Example
290
291    ## Blocking
292
293    ```
294    # #[cfg(feature="blocking")]
295    # fn main() -> sibyl::Result<()> {
296    # let session = sibyl::test_env::get_session()?;
297    let stmt = session.prepare("
298        SELECT employee_id, last_name, first_name
299          FROM hr.employees
300         WHERE manager_id = :id
301    ")?;
302    let rows = stmt.query(103)?;
303    let num_cols = stmt.column_count()?;
304
305    assert_eq!(num_cols, 3);
306    # Ok(())
307    # }
308    # #[cfg(feature="nonblocking")]
309    # fn main() {}
310    ```
311
312    ## Nonblocking
313
314    ```
315    # #[cfg(feature="nonblocking")]
316    # fn main() -> sibyl::Result<()> {
317    # sibyl::block_on(async {
318    # let session = sibyl::test_env::get_session().await?;
319    let stmt = session.prepare("
320        SELECT employee_id, last_name, first_name
321          FROM hr.employees
322         WHERE manager_id = :id
323    ").await?;
324    let rows = stmt.query(103).await?;
325    let num_cols = stmt.column_count()?;
326
327    assert_eq!(num_cols, 3);
328    # Ok(()) })
329    # }
330    # #[cfg(feature="blocking")]
331    # fn main() {}
332    ```
333    */
334    pub fn column_count(&self) -> Result<usize> {
335        let num_columns = self.get_attr::<u32>(OCI_ATTR_PARAM_COUNT)? as usize;
336        Ok( num_columns )
337    }
338
339    /**
340    Returns the number of rows processed/seen so far in SELECT statements.
341
342    For INSERT, UPDATE, and DELETE statements, it is the number of rows processed
343    by the statement.
344
345    For nonscrollable cursors, it is the total number of rows fetched into user buffers
346    since this statement handle was executed. Because they are forward sequential only,
347    this also represents the highest row number seen by the application.
348
349    # Example
350
351    ## Blocking
352
353    ```
354    # #[cfg(feature="blocking")]
355    # fn main() -> sibyl::Result<()> {
356    # let session = sibyl::test_env::get_session()?;
357    let stmt = session.prepare("
358        SELECT employee_id, first_name, last_name
359          FROM hr.employees
360         WHERE manager_id = :id
361      ORDER BY employee_id
362    ")?;
363    stmt.set_prefetch_rows(5)?;
364    let rows = stmt.query(103)?;
365    let mut ids = Vec::new();
366    while let Some( row ) = rows.next()? {
367        // EMPLOYEE_ID is NOT NULL, so we can safely unwrap it
368        let id : u32 = row.get(0)?;
369        ids.push(id);
370    }
371    assert_eq!(stmt.row_count()?, 4);
372    assert_eq!(ids.len(), 4);
373    assert_eq!(ids.as_slice(), &[104 as u32, 105, 106, 107]);
374    # Ok(())
375    # }
376    # #[cfg(feature="nonblocking")]
377    # fn main() {}
378    ```
379
380    ## Nonblocking
381
382    ```
383    # #[cfg(feature="nonblocking")]
384    # fn main() -> sibyl::Result<()> {
385    # sibyl::block_on(async {
386    # let session = sibyl::test_env::get_session().await?;
387    let stmt = session.prepare("
388        SELECT employee_id, first_name, last_name
389          FROM hr.employees
390         WHERE manager_id = :id
391      ORDER BY employee_id
392    ").await?;
393    stmt.set_prefetch_rows(5)?;
394    let rows = stmt.query(103).await?;
395    let mut ids = Vec::new();
396    while let Some( row ) = rows.next().await? {
397        let id : i32 = row.get(0)?;
398        ids.push(id);
399    }
400    assert_eq!(stmt.row_count()?, 4);
401    assert_eq!(ids.len(), 4);
402    assert_eq!(ids.as_slice(), &[104, 105, 106, 107]);
403    # Ok(()) })
404    # }
405    # #[cfg(feature="blocking")]
406    # fn main() {}
407    ```
408    */
409    pub fn row_count(&self) -> Result<usize> {
410        let num_rows = self.get_attr::<u64>(OCI_ATTR_UB8_ROW_COUNT)? as usize;
411        Ok( num_rows )
412    }
413
414    // Indicates the number of rows that were successfully fetched into the user's buffers
415    // in the last fetch or execute with nonzero iterations.
416    //
417    // This is not very useful in this implementation as we set up buffers for 1 row only.
418    //
419    // pub fn rows_fetched(&self) -> Result<usize> {
420    //     let num_rows = self.get_attr::<u32>(OCI_ATTR_ROWS_FETCHED)? as usize;
421    //     Ok( num_rows )
422    // }
423
424    /**
425    Checks whether the value returned for the output parameter is NULL.
426
427    # Parameters
428
429    * `pos` - parameter "position" - either the parameter name or a zero-based index
430
431    # Example
432
433    ## Blocking
434
435    ```
436    # #[cfg(feature="blocking")]
437    # fn main() -> sibyl::Result<()> {
438    # let session = sibyl::test_env::get_session()?;
439    let stmt = session.prepare("
440        UPDATE hr.employees
441           SET manager_id = :NEW_MANAGER_ID
442         WHERE employee_id = :EMPLOYEE_ID
443        RETURN commission_pct
444          INTO :COMMISSION_PCT
445    ")?;
446    let mut commission_pct = 0f64;
447    stmt.execute((
448        (":EMPLOYEE_ID", 133),
449        (":NEW_MANAGER_ID", 120),
450        (":COMMISSION_PCT", &mut commission_pct),
451    ))?;
452    let commission_pct_is_null = stmt.is_null(":COMMISSION_PCT")?;
453    assert!(commission_pct_is_null);
454
455    // Alternatively an Option could be bound to a RETURNING
456    // parameter that might be NULL
457    let mut commission_pct = Some(0u64);
458    stmt.execute((
459        (":EMPLOYEE_ID", 133),
460        (":NEW_MANAGER_ID", 120),
461        (":COMMISSION_PCT", &mut commission_pct),
462    ))?;
463    assert!(commission_pct.is_none());
464    # session.rollback()?;
465    # Ok(())
466    # }
467    # #[cfg(feature="nonblocking")]
468    # fn main() {}
469    ```
470
471    ## Nonblocking
472
473    ```
474    # #[cfg(feature="nonblocking")]
475    # fn main() -> sibyl::Result<()> {
476    # sibyl::block_on(async {
477    # let session = sibyl::test_env::get_session().await?;
478    let stmt = session.prepare("
479        UPDATE hr.employees
480           SET manager_id = :NEW_MANAGER_ID
481         WHERE employee_id = :EMPLOYEE_ID
482        RETURN commission_pct
483          INTO :COMMISSION_PCT
484    ").await?;
485    let mut commission_pct = 0f64;
486    stmt.execute((
487        (":EMPLOYEE_ID", 133),
488        (":NEW_MANAGER_ID", 120),
489        (":COMMISSION_PCT", &mut commission_pct)
490    )).await?;
491    let commission_pct_is_null = stmt.is_null(":COMMISSION_PCT")?;
492    assert!(commission_pct_is_null);
493
494    // Alternatively an Option could be bound to a RETURNING
495    // parameter that might be NULL
496    let mut commission_pct = Some(0u64);
497    stmt.execute((
498        (":EMPLOYEE_ID", 133),
499        (":NEW_MANAGER_ID", 120),
500        (":COMMISSION_PCT", &mut commission_pct),
501    )).await?;
502    assert!(commission_pct.is_none());
503    # session.rollback().await?;
504    # Ok(()) })
505    # }
506    # #[cfg(feature="blocking")]
507    # fn main() {}
508    ```
509    */
510    pub fn is_null(&self, pos: impl Position) -> Result<bool> {
511        self.params.as_ref().map(|params| params.read().is_null(pos)).unwrap_or(Ok(true))
512    }
513
514    /**
515    Returns the size of the data in bytes bound to the specified parameter placeholder.
516
517    This is the most useful for byte arrays bound to OUT or INOUT parameters. Unlike `String`
518    or `Vec` byte slices cannot adjust their length when the size of the returned data is
519    smaller than their size. This method can be used to do so after the data are fetched.
520
521    # Example
522
523    ## Blocking
524
525    ```
526    # #[cfg(feature="blocking")]
527    # fn main() -> sibyl::Result<()> {
528    # let session = sibyl::test_env::get_session()?;
529    let stmt = session.prepare("
530    BEGIN
531        :VAL := Utl_Raw.Cast_To_Raw('data');
532    END;
533    ")?;
534    let mut data = [0; 8];
535    stmt.execute(data.as_mut())?;
536
537    assert_eq!(data, [0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00]);
538    // Note the "trailing" original zeros ----^^^^--^^^^--^^^^--^^^^
539    assert_eq!(stmt.len_of("VAL")?, 4);
540
541    let res = data[0..stmt.len_of("VAL")?].as_ref();
542    assert_eq!(res.len(), 4);
543    # Ok(())
544    # }
545    # #[cfg(feature="nonblocking")]
546    # fn main() {}
547    ```
548
549    ## Nonblocking
550
551    ```
552    # #[cfg(feature="nonblocking")]
553    # fn main() -> sibyl::Result<()> {
554    # sibyl::block_on(async {
555    # let session = sibyl::test_env::get_session().await?;
556    let stmt = session.prepare("
557    BEGIN
558        :VAL := Utl_Raw.Cast_To_Raw('data');
559    END;
560    ").await?;
561    let mut data = [0; 8];
562    stmt.execute(data.as_mut()).await?;
563
564    assert_eq!(data, [0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00]);
565    assert_eq!(stmt.len_of(0)?, 4);
566
567    let res = data[0..stmt.len_of(0)?].as_ref();
568    assert_eq!(res.len(), 4);
569    # Ok(()) })
570    # }
571    # #[cfg(feature="blocking")]
572    # fn main() {}
573    ```
574    */
575    pub fn len_of(&self, pos: impl Position) -> Result<usize> {
576        self.params.as_ref().map(|params| params.read().data_len(pos)).unwrap_or(Ok(0))
577    }
578
579    /**
580    Returns column meta data.
581
582    Returns None if the specified position is greater than the number of columns in the query
583    or if the prepared statement is not a SELECT and has no columns.
584
585    # Parameters
586
587    * `pos` - zero-based column position
588
589    # Example
590
591    ## Blocking
592
593    ```
594    use sibyl::ColumnType;
595
596    # #[cfg(feature="blocking")]
597    # fn main() -> sibyl::Result<()> {
598    # let session = sibyl::test_env::get_session()?;
599    let stmt = session.prepare("
600        SELECT employee_id, last_name, first_name
601          FROM hr.employees
602         WHERE manager_id = :id
603    ")?;
604    let rows = stmt.query(103)?;
605    let col = stmt.column(0).expect("employee_id column info");
606
607    assert_eq!(col.name()?, "EMPLOYEE_ID");
608    assert_eq!(col.data_type()?, ColumnType::Number);
609    assert_eq!(col.precision()?, 6);
610    assert_eq!(col.scale()?, 0);
611    assert!(!col.is_null()?);
612    assert!(col.is_visible()?);
613    assert!(!col.is_identity()?);
614    # Ok(())
615    # }
616    # #[cfg(feature="nonblocking")]
617    # fn main() {}
618    ```
619
620    ## Nonblocking
621
622    ```
623    use sibyl::ColumnType;
624
625    # #[cfg(feature="nonblocking")]
626    # fn main() -> sibyl::Result<()> {
627    # sibyl::block_on(async {
628    # let session = sibyl::test_env::get_session().await?;
629    let stmt = session.prepare("
630        SELECT employee_id, last_name, first_name
631          FROM hr.employees
632         WHERE manager_id = :id
633    ").await?;
634    let rows = stmt.query(103).await?;
635    let col = stmt.column(0).expect("employee_id column info");
636
637    assert_eq!(col.name()?, "EMPLOYEE_ID");
638    assert_eq!(col.data_type()?, ColumnType::Number);
639    assert_eq!(col.precision()?, 6);
640    assert_eq!(col.scale()?, 0);
641    assert!(!col.is_null()?);
642    assert!(col.is_visible()?);
643    assert!(!col.is_identity()?);
644    # Ok(()) })
645    # }
646    # #[cfg(feature="blocking")]
647    # fn main() {}
648    ```
649    */
650    pub fn column(&self, pos: usize) -> Option<ColumnInfo<'_>> {
651        self.cols.get()
652            .and_then(|cols|
653                cols.read().column_param(pos)
654            ).map(|param|
655                ColumnInfo::new(param, self.as_ref())
656            )
657    }
658}