odbc_iter/
result_set.rs

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/// Error crating ResultSet iterator.
15#[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/// Errors related to data access of query result set.
57///
58/// This error can happen when iterating rows of executed query result set.
59/// For convenience this error can be converted into `QueryError`.
60#[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
113/// Iterator over result set rows.
114///
115/// Items of this iterator can be of any type that implements `TryFromRow` that includes common Rust types and tuples.
116pub 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        // We need to make sure statement is dropped; implementing Drop forces use of drop(row_iter) if not consumed before another query
140        // Should Statement not impl Drop itself?
141        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")?; // don't reference parameter data any more
173
174                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")?; // don't reference parameter data any more
200                (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        // convert schema here so that when iterating rows we can pass reference to it per row for row type conversion
211        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    /// Information about column types.
228    pub fn schema(&self) -> &[ColumnType] {
229        self.schema.as_slice()
230    }
231
232    /// Get associated data access configuration object.
233    pub fn configuration(&self) -> &C {
234        &self.configuration
235    }
236
237    /// Get exactly one row from the result set.
238    /// This function will fail if zero or more than one rows would be provided.
239    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    /// Get first row from the result set.
252    /// Any following rows are discarded.
253    /// This function will fail no rows were provided.
254    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    /// Assert that the query returned no rows.
261    /// This function will fail if there was at least one row provided.
262    /// This is useful when working with SQL statements that produce no rows like "INSERT".
263    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    /// Close the result set and discard any not consumed rows.
278    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    /// When available provides information on number of rows affected by query (e.g. "DELETE" statement).
292    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    /// Close the result set and discard any not consumed rows.
310    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    /// When available provides information on number of rows affected by query (e.g. "DELETE" statement).
320    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        // Invalid cursor
350        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}