1use error_context::prelude::*;
2use log::{debug, log_enabled, trace};
3use odbc::{ColumnDescriptor, DiagnosticRecord, Executed, Prepared, ResultSetState};
4use std::convert::TryFrom;
5use std::error::Error;
6use std::fmt;
7use std::marker::PhantomData;
8
9use crate::query::{Handle, PreparedStatement};
10use crate::row::{Settings, Configuration, ColumnType, DatumAccessError, Row, TryFromRow, UnsupportedSqlDataType};
11use crate::OdbcError;
12use crate::stats::QueryFetchingGuard;
13
14#[derive(Debug)]
16#[allow(clippy::large_enum_variant)]
17pub enum ResultSetError {
18 OdbcError(DiagnosticRecord, &'static str),
19 UnsupportedSqlDataType(UnsupportedSqlDataType),
20}
21
22impl fmt::Display for ResultSetError {
23 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24 match self {
25 ResultSetError::OdbcError(_, context) => {
26 write!(f, "ODBC call failed while {}", context)
27 }
28 ResultSetError::UnsupportedSqlDataType(_) => {
29 write!(f, "query schema has unsupported data type")
30 }
31 }
32 }
33}
34
35impl Error for ResultSetError {
36 fn source(&self) -> Option<&(dyn Error + 'static)> {
37 match self {
38 ResultSetError::OdbcError(err, _) => Some(err),
39 ResultSetError::UnsupportedSqlDataType(err) => Some(err),
40 }
41 }
42}
43
44impl From<ErrorContext<DiagnosticRecord, &'static str>> for ResultSetError {
45 fn from(err: ErrorContext<DiagnosticRecord, &'static str>) -> ResultSetError {
46 ResultSetError::OdbcError(err.error, err.context)
47 }
48}
49
50impl From<UnsupportedSqlDataType> for ResultSetError {
51 fn from(err: UnsupportedSqlDataType) -> ResultSetError {
52 ResultSetError::UnsupportedSqlDataType(err)
53 }
54}
55
56#[derive(Debug)]
61#[allow(clippy::large_enum_variant)]
62pub enum DataAccessError {
63 OdbcError(DiagnosticRecord, &'static str),
64 DatumAccessError(DatumAccessError),
65 FromRowError(Box<dyn Error>),
66 UnexpectedNumberOfRows(&'static str),
67}
68
69impl fmt::Display for DataAccessError {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 match self {
72 DataAccessError::OdbcError(_, context) => {
73 write!(f, "ODBC call failed while {}", context)
74 }
75 DataAccessError::DatumAccessError(_) => {
76 write!(f, "failed to access datum in ODBC cursor")
77 }
78 DataAccessError::FromRowError(_) => {
79 write!(f, "failed to convert table row to target type")
80 }
81 DataAccessError::UnexpectedNumberOfRows(context) => write!(
82 f,
83 "unexpected number of rows returned by query: {}",
84 context
85 ),
86 }
87 }
88}
89
90impl Error for DataAccessError {
91 fn source(&self) -> Option<&(dyn Error + 'static)> {
92 match self {
93 DataAccessError::OdbcError(err, _) => Some(err),
94 DataAccessError::DatumAccessError(err) => Some(err),
95 DataAccessError::FromRowError(err) => Some(err.as_ref()),
96 DataAccessError::UnexpectedNumberOfRows(_) => None,
97 }
98 }
99}
100
101impl From<ErrorContext<DiagnosticRecord, &'static str>> for DataAccessError {
102 fn from(err: ErrorContext<DiagnosticRecord, &'static str>) -> DataAccessError {
103 DataAccessError::OdbcError(err.error, err.context)
104 }
105}
106
107impl From<DatumAccessError> for DataAccessError {
108 fn from(err: DatumAccessError) -> DataAccessError {
109 DataAccessError::DatumAccessError(err)
110 }
111}
112
113pub struct ResultSet<'h, 'c, V, S, C: Configuration> {
117 statement: Option<ExecutedStatement<'c, S>>,
118 schema: Vec<ColumnType>,
119 columns: i16,
120 settings: &'c Settings,
121 configuration: C,
122 phantom: PhantomData<&'h V>,
123 _stats_guard: QueryFetchingGuard,
124}
125
126impl<'h, 'c, V, S, C: Configuration> fmt::Debug for ResultSet<'h, 'c, V, S, C> {
127 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
128 f.debug_struct("ResultSet")
129 .field("schema", &self.schema)
130 .field("columns", &self.columns)
131 .field("settings", &self.settings)
132 .field("configuration", &self.configuration)
133 .finish()
134 }
135}
136
137impl<'h, 'c, V, S, C: Configuration> Drop for ResultSet<'h, 'c, V, S, C> {
138 fn drop(&mut self) {
139 drop(self.statement.take())
142 }
143}
144
145enum ExecutedStatement<'c, S> {
146 HasResult(odbc::Statement<'c, 'c, S, odbc::HasResult>),
147 NoResult(odbc::Statement<'c, 'c, S, odbc::NoResult>),
148}
149
150impl<'h, 'c: 'h, V, S, C: Configuration> ResultSet<'h, 'c, V, S, C>
151where
152 V: TryFromRow<C>,
153{
154 pub(crate) fn from_result(
155 _handle: &'h Handle<'c, C>,
156 result: ResultSetState<'c, '_, S>,
157 stats_guard: QueryFetchingGuard,
158 settings: &'c Settings,
159 configuration: C,
160 ) -> Result<ResultSet<'h, 'c, V, S, C>, ResultSetError> {
161 let (odbc_schema, columns, statement) = match result {
162 ResultSetState::Data(statement) => {
163 let columns = statement
164 .num_result_cols()
165 .wrap_error_while("getting number of result columns")?;
166 let odbc_schema = (1..=columns)
167 .map(|i| statement.describe_col(i as u16))
168 .collect::<Result<Vec<ColumnDescriptor>, _>>()
169 .wrap_error_while("getting column descriptiors")?;
170 let statement = statement
171 .reset_parameters()
172 .wrap_error_while("reseting bound parameters on statement")?; if log_enabled!(::log::Level::Debug) {
175 if odbc_schema.is_empty() {
176 debug!("Got empty data set");
177 } else {
178 debug!(
179 "Got data with columns: {}",
180 odbc_schema
181 .iter()
182 .map(|cd| cd.name.clone())
183 .collect::<Vec<String>>()
184 .join(", ")
185 );
186 }
187 }
188
189 (
190 odbc_schema,
191 columns,
192 ExecutedStatement::HasResult(statement),
193 )
194 }
195 ResultSetState::NoData(statement) => {
196 debug!("No data");
197 let statement = statement
198 .reset_parameters()
199 .wrap_error_while("reseting bound parameters on statement")?; (Vec::new(), 0, ExecutedStatement::NoResult(statement))
201 }
202 };
203
204 if log_enabled!(::log::Level::Trace) {
205 for cd in &odbc_schema {
206 trace!("ODBC query result schema: {} [{:?}] size: {:?} nullable: {:?} decimal_digits: {:?}", cd.name, cd.data_type, cd.column_size, cd.nullable, cd.decimal_digits);
207 }
208 }
209
210 let schema = odbc_schema
212 .into_iter()
213 .map(TryFrom::try_from)
214 .collect::<Result<Vec<_>, _>>()?;
215
216 Ok(ResultSet {
217 statement: Some(statement),
218 schema,
219 columns,
220 phantom: PhantomData,
221 settings,
222 configuration,
223 _stats_guard: stats_guard,
224 })
225 }
226
227 pub fn schema(&self) -> &[ColumnType] {
229 self.schema.as_slice()
230 }
231
232 pub fn configuration(&self) -> &C {
234 &self.configuration
235 }
236
237 pub fn single(mut self) -> Result<V, DataAccessError> {
240 let value = self.next().ok_or(DataAccessError::UnexpectedNumberOfRows(
241 "expected single row but got no rows",
242 ))?;
243 if self.next().is_some() {
244 return Err(DataAccessError::UnexpectedNumberOfRows(
245 "expected single row but got more rows",
246 ));
247 }
248 value
249 }
250
251 pub fn first(mut self) -> Result<V, DataAccessError> {
255 self.next().ok_or(DataAccessError::UnexpectedNumberOfRows(
256 "expected at least one row but got no rows",
257 ))?
258 }
259
260 pub fn no_result(mut self) -> Result<(), DataAccessError> {
264 if self.next().is_some() {
265 return Err(DataAccessError::UnexpectedNumberOfRows(
266 "exepcted no rows but got at least one",
267 ));
268 }
269 Ok(())
270 }
271}
272
273impl<'h, 'c: 'h, V, C: Configuration> ResultSet<'h, 'c, V, Prepared, C>
274where
275 V: TryFromRow<C>,
276{
277 pub fn close(mut self) -> Result<PreparedStatement<'c>, OdbcError> {
279 match self.statement.take().unwrap() {
280 ExecutedStatement::HasResult(statement) => Ok(PreparedStatement::from_statement(
281 statement
282 .close_cursor()
283 .wrap_error_while("closing cursor on executed prepared statement")?,
284 )),
285 ExecutedStatement::NoResult(statement) => {
286 Ok(PreparedStatement::from_statement(statement))
287 }
288 }
289 }
290
291 pub fn affected_rows(&self) -> Result<Option<i64>, OdbcError> {
293 match &self.statement.as_ref().unwrap() {
294 ExecutedStatement::HasResult(statement) => {
295 let rows = statement.affected_row_count().wrap_error_while(
296 "getting affected row count from prepared statemnt with result",
297 )?;
298 Ok(if rows >= 0 { Some(rows as i64) } else { None })
299 }
300 ExecutedStatement::NoResult(_) => Ok(None),
301 }
302 }
303}
304
305impl<'h, 'c: 'h, V, C: Configuration> ResultSet<'h, 'c, V, Executed, C>
306where
307 V: TryFromRow<C>,
308{
309 pub fn close(mut self) -> Result<(), OdbcError> {
311 if let ExecutedStatement::HasResult(statement) = self.statement.take().unwrap() {
312 statement
313 .close_cursor()
314 .wrap_error_while("closing cursor on executed statement")?;
315 }
316 Ok(())
317 }
318
319 pub fn affected_rows(&self) -> Result<Option<i64>, OdbcError> {
321 let rows = match &self.statement.as_ref().unwrap() {
322 ExecutedStatement::HasResult(statement) => {
323 statement.affected_row_count().wrap_error_while(
324 "getting affected row count from allocated statemnt with result",
325 )?
326 }
327 ExecutedStatement::NoResult(statement) => {
328 statement.affected_row_count().wrap_error_while(
329 "getting affected row count from allocated statemnt with no result",
330 )?
331 }
332 };
333 Ok(if rows >= 0 { Some(rows as i64) } else { None })
334 }
335}
336
337impl<'h, 'c: 'h, V, S, C: Configuration> Iterator for ResultSet<'h, 'c, V, S, C>
338where
339 V: TryFromRow<C>,
340{
341 type Item = Result<V, DataAccessError>;
342
343 fn next(&mut self) -> Option<Self::Item> {
344 let statement = match self.statement.as_mut().unwrap() {
345 ExecutedStatement::HasResult(statement) => statement,
346 ExecutedStatement::NoResult(_) => return None,
347 };
348
349 if self.columns == 0 {
351 return None;
352 }
353
354 let settings = self.settings;
355 let configuration = &self.configuration;
356 let schema = &self.schema;
357
358 statement
359 .fetch()
360 .wrap_error_while("fetching row")
361 .transpose()
362 .map(|cursor| {
363 let row = Row::new(cursor?, schema, settings, configuration);
364 TryFromRow::try_from_row(row)
365 .map_err(|err| DataAccessError::FromRowError(Box::new(err)))
366 })
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 #[allow(unused_imports)]
373 use crate::{Odbc, TryFromRow, Configuration, ValueRow, ColumnType, Value, DatumAccessError, Row};
374 #[allow(unused_imports)]
375 use assert_matches::assert_matches;
376
377 #[derive(Debug)]
378 struct Foo {
379 val: i64,
380 }
381
382 impl<C: Configuration> TryFromRow<C> for Foo {
383 type Error = DatumAccessError;
384 fn try_from_row<'r, 's, 'c, S>(mut row: Row<'r, 's, 'c, S, C>) -> Result<Self, Self::Error> {
385 Ok(Foo {
386 val: row.shift_column().expect("column").into_i64()?.expect("value")
387 })
388 }
389 }
390
391 #[test]
392 #[cfg(feature = "test-monetdb")]
393 fn test_custom_type() {
394 let mut db = crate::tests::connect_monetdb();
395
396 let foo: Foo = db
397 .handle()
398 .query("SELECT CAST(42 AS BIGINT) AS val;")
399 .expect("failed to run query")
400 .single()
401 .expect("fetch data");
402
403 assert_eq!(foo.val, 42);
404 }
405
406 #[test]
407 #[cfg(feature = "test-monetdb")]
408 fn test_single_value() {
409 let mut db = crate::tests::connect_monetdb();
410
411 let value: Value = db
412 .handle()
413 .query("SELECT CAST(42 AS BIGINT)")
414 .expect("failed to run query")
415 .single()
416 .expect("fetch data");
417
418 assert_eq!(value.to_i64().unwrap(), 42);
419 }
420
421 #[test]
422 #[cfg(feature = "test-monetdb")]
423 fn test_single_nullable_value() {
424 let mut db = crate::tests::connect_monetdb();
425
426 let value: Option<Value> = db
427 .handle()
428 .query("SELECT CAST(42 AS BIGINT)")
429 .expect("failed to run query")
430 .single()
431 .expect("fetch data");
432
433 assert!(value.is_some());
434 assert_eq!(value.unwrap().to_i64().unwrap(), 42);
435
436 let value: Option<Value> = db
437 .handle()
438 .query("SELECT CAST(NULL AS BIGINT)")
439 .expect("failed to run query")
440 .single()
441 .expect("fetch data");
442
443 assert!(value.is_none());
444 }
445
446 #[test]
447 #[cfg(feature = "test-monetdb")]
448 fn test_value_row() {
449 let mut db = crate::tests::connect_monetdb();
450
451 let value: ValueRow = db
452 .handle()
453 .query("SELECT CAST(42 AS BIGINT), CAST(22 AS INTEGER)")
454 .expect("failed to run query")
455 .single()
456 .expect("fetch data");
457
458 assert_eq!(value.len(), 2);
459 assert_eq!(value[0].as_ref().unwrap().to_i64().unwrap(), 42);
460 assert_eq!(value[1].as_ref().unwrap().to_i32().unwrap(), 22);
461 }
462
463 #[test]
464 #[cfg(feature = "test-monetdb")]
465 fn test_single_copy() {
466 let mut db = crate::tests::connect_monetdb();
467
468 let value: bool = db
469 .handle()
470 .query("SELECT true")
471 .expect("failed to run query")
472 .single()
473 .expect("fetch data");
474
475 assert_eq!(value, true);
476
477 let value: Option<bool> = db
478 .handle()
479 .query("SELECT true")
480 .expect("failed to run query")
481 .single()
482 .expect("fetch data");
483
484 assert_eq!(value.unwrap(), true);
485
486 let value: Option<bool> = db
487 .handle()
488 .query("SELECT CAST(NULL AS BOOL)")
489 .expect("failed to run query")
490 .single()
491 .expect("fetch data");
492
493 assert!(value.is_none());
494
495 let value: i64 = db
496 .handle()
497 .query("SELECT CAST(42 AS BIGINT)")
498 .expect("failed to run query")
499 .single()
500 .expect("fetch data");
501
502 assert_eq!(value, 42);
503
504 let value: Option<i64> = db
505 .handle()
506 .query("SELECT CAST(42 AS BIGINT)")
507 .expect("failed to run query")
508 .single()
509 .expect("fetch data");
510
511 assert_eq!(value.unwrap(), 42i64);
512
513 let value: Option<i64> = db
514 .handle()
515 .query("SELECT CAST(NULL AS BIGINT)")
516 .expect("failed to run query")
517 .single()
518 .expect("fetch data");
519
520 assert!(value.is_none());
521 }
522
523 #[test]
524 #[cfg(feature = "test-monetdb")]
525 fn test_single_unsigned() {
526 let mut db = crate::tests::connect_monetdb();
527
528 let value: Option<u64> = db
529 .handle()
530 .query("SELECT CAST(42 AS BIGINT)")
531 .expect("failed to run query")
532 .single()
533 .expect("fetch data");
534
535 assert_eq!(value.unwrap(), 42u64);
536 }
537
538 #[test]
539 #[cfg(feature = "test-monetdb")]
540 #[should_panic(expected = "ValueOutOfRange")]
541 fn test_single_unsigned_err() {
542 let mut db = crate::tests::connect_monetdb();
543
544 let _value: Option<u64> = db
545 .handle()
546 .query("SELECT CAST(-666 AS BIGINT)")
547 .expect("failed to run query")
548 .single()
549 .expect("fetch data");
550 }
551
552 #[test]
553 #[cfg(feature = "test-monetdb")]
554 fn test_single_string() {
555 let mut db = crate::tests::connect_monetdb();
556
557 let value: String = db
558 .handle()
559 .query("SELECT 'foo'")
560 .expect("failed to run query")
561 .single()
562 .expect("fetch data");
563
564 assert_eq!(&value, "foo");
565
566 let value: Option<String> = db
567 .handle()
568 .query("SELECT 'foo'")
569 .expect("failed to run query")
570 .single()
571 .expect("fetch data");
572
573 assert_eq!(&value.unwrap(), "foo");
574
575 let value: Option<String> = db
576 .handle()
577 .query("SELECT CAST(NULL AS STRING)")
578 .expect("failed to run query")
579 .single()
580 .expect("fetch data");
581
582 assert!(value.is_none());
583 }
584
585 #[test]
586 #[cfg(feature = "chrono")]
587 #[cfg(feature = "test-monetdb")]
588 fn test_single_date() {
589 use chrono::Datelike;
590 use chrono::NaiveDate;
591
592 let mut db = crate::tests::connect_monetdb();
593
594 let value: NaiveDate = db
595 .handle()
596 .query("SELECT CAST('2019-04-02' AS DATE)")
597 .expect("failed to run query")
598 .single()
599 .expect("fetch data");
600
601 assert_eq!(value.year(), 2019);
602 assert_eq!(value.month(), 4);
603 assert_eq!(value.day(), 2);
604
605 let value: Option<NaiveDate> = db
606 .handle()
607 .query("SELECT CAST('2019-04-02' AS DATE)")
608 .expect("failed to run query")
609 .single()
610 .expect("fetch data");
611
612 assert_eq!(value.unwrap().year(), 2019);
613 assert_eq!(value.unwrap().month(), 4);
614 assert_eq!(value.unwrap().day(), 2);
615
616 let value: Option<NaiveDate> = db
617 .handle()
618 .query("SELECT CAST(NULL AS DATE)")
619 .expect("failed to run query")
620 .single()
621 .expect("fetch data");
622
623 assert!(value.is_none());
624 }
625
626 #[test]
627 #[cfg(feature = "test-monetdb")]
628 fn test_tuple_value() {
629 let mut db = crate::tests::connect_monetdb();
630
631 let value: (String, i64, bool) = db
632 .handle()
633 .query("SELECT 'foo', CAST(42 AS BIGINT), true")
634 .expect("failed to run query")
635 .single()
636 .expect("fetch data");
637
638 assert_eq!(&value.0, "foo");
639 assert_eq!(value.1, 42);
640 assert_eq!(value.2, true);
641
642 let value: (Option<String>, i64, Option<bool>) = db
643 .handle()
644 .query("SELECT 'foo', CAST(42 AS BIGINT), true")
645 .expect("failed to run query")
646 .single()
647 .expect("fetch data");
648
649 assert_eq!(&value.0.unwrap(), "foo");
650 assert_eq!(value.1, 42);
651 assert_eq!(value.2.unwrap(), true);
652
653 let value: (Option<String>, i64, Option<bool>) = db
654 .handle()
655 .query("SELECT CAST(NULL AS STRING), CAST(42 AS BIGINT), CAST(NULL AS BOOL)")
656 .expect("failed to run query")
657 .single()
658 .expect("fetch data");
659
660 assert!(&value.0.is_none());
661 assert_eq!(value.1, 42);
662 assert!(value.2.is_none());
663 }
664
665 #[test]
666 #[cfg(feature = "test-monetdb")]
667 #[cfg(feature = "serde_json")]
668 fn test_single_json() {
669 let mut db = crate::tests::connect_monetdb();
670
671 let value: serde_json::Value = db
672 .handle()
673 .query(r#"SELECT CAST('{ "foo": 42 }' AS JSON)"#)
674 .expect("failed to run query")
675 .single()
676 .expect("fetch data");
677
678 assert_eq!(value.pointer("/foo").unwrap().as_i64().unwrap(), 42);
679
680 let value: Option<serde_json::Value> = db
681 .handle()
682 .query(r#"SELECT CAST('{ "foo": 42 }' AS JSON)"#)
683 .expect("failed to run query")
684 .single()
685 .expect("fetch data");
686
687 assert_eq!(value.unwrap().pointer("/foo").unwrap().as_i64().unwrap(), 42);
688
689 let value: Option<serde_json::Value> = db
690 .handle()
691 .query("SELECT CAST(NULL AS JSON)")
692 .expect("failed to run query")
693 .single()
694 .expect("fetch data");
695
696 assert!(value.is_none());
697 }
698
699 #[test]
700 #[cfg(feature = "test-monetdb")]
701 #[cfg(feature = "serde_json")]
702 fn test_single_json_as_string() {
703 let mut db = crate::tests::connect_monetdb();
704
705 let value: String = db
706 .handle()
707 .query(r#"SELECT CAST('{ "foo": 42 }' AS JSON)"#)
708 .expect("failed to run query")
709 .single()
710 .expect("fetch data");
711
712 assert_eq!(&value, r#"{ "foo": 42 }"#);
713
714 let value: Option<String> = db
715 .handle()
716 .query(r#"SELECT CAST('{ "foo": 42 }' AS JSON)"#)
717 .expect("failed to run query")
718 .single()
719 .expect("fetch data");
720
721 assert_eq!(&value.unwrap(), r#"{ "foo": 42 }"#);
722
723 let value: Option<String> = db
724 .handle()
725 .query("SELECT CAST(NULL AS JSON)")
726 .expect("failed to run query")
727 .single()
728 .expect("fetch data");
729
730 assert!(value.is_none());
731 }
732}