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}