diff --git a/Cargo.lock b/Cargo.lock index a80ad1fd..a468d60e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "odbc-api" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "csv", @@ -770,7 +770,7 @@ checksum = "5fa96a125cee9f1fe33cb69d6517466afc4e9cb1f44afdcc67210e2776aec7b2" [[package]] name = "odbcsv" -version = "0.3.67" +version = "0.3.68" dependencies = [ "anyhow", "assert_cmd", diff --git a/Changelog.md b/Changelog.md index a32696ad..062a56c4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ # Changelog +## 0.38.0 + +* Add column buffer index to allocation errors to provide more context + ## 0.37.0 * Add fallibale allocation of binary columns. diff --git a/odbc-api/Cargo.toml b/odbc-api/Cargo.toml index 1dbaad67..33effa97 100644 --- a/odbc-api/Cargo.toml +++ b/odbc-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "odbc-api" -version = "0.37.0" +version = "0.38.0" authors = ["Markus Klein"] edition = "2021" license = "MIT" diff --git a/odbc-api/src/buffers/any_column_buffer.rs b/odbc-api/src/buffers/any_column_buffer.rs index 59c4ac60..87bf9e35 100644 --- a/odbc-api/src/buffers/any_column_buffer.rs +++ b/odbc-api/src/buffers/any_column_buffer.rs @@ -3,6 +3,7 @@ use std::{collections::HashSet, ffi::c_void}; use odbc_sys::{CDataType, Date, Time, Timestamp}; use crate::{ + error::TooLargeBufferSize, handles::{CData, CDataMut, HasDataType}, Bit, DataType, Error, }; @@ -59,7 +60,10 @@ pub enum AnyColumnBuffer { impl AnyColumnBuffer { /// Map buffer description to actual buffer. - pub fn from_description(max_rows: usize, desc: BufferDescription) -> Result { + pub fn from_description( + max_rows: usize, + desc: BufferDescription, + ) -> Result { let buffer = match (desc.kind, desc.nullable) { (BufferKind::Binary { length }, _) => { AnyColumnBuffer::Binary(BinColumn::new(max_rows as usize, length)?) @@ -270,11 +274,10 @@ pub fn buffer_from_description( let mut column_index = 0; let columns = descs .map(move |desc| { + let buffer = AnyColumnBuffer::from_description(capacity, desc) + .map_err(|source| source.add_context(column_index))?; column_index += 1; - Ok(( - column_index, - AnyColumnBuffer::from_description(capacity, desc)?, - )) + Ok((column_index, buffer)) }) .collect::>()?; Ok(unsafe { ColumnarBuffer::new_unchecked(capacity, columns) }) @@ -292,7 +295,8 @@ pub fn buffer_from_description_and_indices( .map(|(col_index, buffer_desc)| { Ok(( col_index, - AnyColumnBuffer::from_description(max_rows, buffer_desc)?, + AnyColumnBuffer::from_description(max_rows, buffer_desc) + .map_err(|source| source.add_context(col_index - 1))?, )) }) .collect::>()?; diff --git a/odbc-api/src/buffers/bin_column.rs b/odbc-api/src/buffers/bin_column.rs index ec06f2da..a65c2859 100644 --- a/odbc-api/src/buffers/bin_column.rs +++ b/odbc-api/src/buffers/bin_column.rs @@ -1,7 +1,8 @@ use crate::{ buffers::Indicator, + error::TooLargeBufferSize, handles::{CData, CDataMut, HasDataType}, - DataType, Error, + DataType, }; use log::debug; @@ -26,7 +27,7 @@ pub struct BinColumn { impl BinColumn { /// This will allocate a value and indicator buffer for `batch_size` elements. Each value may /// have a maximum length of `max_len`. - pub fn new(batch_size: usize, element_size: usize) -> Result { + pub fn new(batch_size: usize, element_size: usize) -> Result { // Use a fallibale allocation for creating the buffer. In applications often the max_len // size of the buffer, might be directly inspired by the maximum size of the type, as // reported, by ODBC. Which might get exceedingly large for types like VARBINARY(MAX), or @@ -35,7 +36,7 @@ impl BinColumn { let mut values = Vec::new(); values .try_reserve_exact(len) - .map_err(|_| Error::TooLargeColumnBufferSize { + .map_err(|_| TooLargeBufferSize { num_elements: batch_size, element_size, })?; @@ -454,7 +455,7 @@ unsafe impl CDataMut for BinColumn { #[cfg(test)] mod test { - use crate::Error; + use crate::error::TooLargeBufferSize; use super::BinColumn; @@ -465,7 +466,7 @@ mod test { let error = result.unwrap_err(); assert!(matches!( error, - Error::TooLargeColumnBufferSize { + TooLargeBufferSize { num_elements: 10_000, element_size: 2_147_483_648 } diff --git a/odbc-api/src/buffers/columnar.rs b/odbc-api/src/buffers/columnar.rs index 823c137c..a7d66595 100644 --- a/odbc-api/src/buffers/columnar.rs +++ b/odbc-api/src/buffers/columnar.rs @@ -411,7 +411,16 @@ impl TextRowSet { let max_str_len = max_str_len .map(|limit| min(limit, reported_len)) .unwrap_or(reported_len); - Ok((col_index, TextColumn::new(batch_size, max_str_len)?)) + Ok(( + col_index, + TextColumn::new(batch_size, max_str_len).map_err(|source| { + Error::TooLargeColumnBufferSize { + buffer_index: col_index - 1, + num_elements: source.num_elements, + element_size: source.element_size, + } + })?, + )) }) .collect::>()?; Ok(TextRowSet { @@ -432,7 +441,8 @@ impl TextRowSet { .map(|(index, max_str_len)| { Ok(( (index + 1).try_into().unwrap(), - TextColumn::new(row_capacity, max_str_len)?, + TextColumn::new(row_capacity, max_str_len) + .map_err(|source| source.add_context(index.try_into().unwrap()))?, )) }) .collect::>()?; diff --git a/odbc-api/src/buffers/text_column.rs b/odbc-api/src/buffers/text_column.rs index bf27817d..d119e4f1 100644 --- a/odbc-api/src/buffers/text_column.rs +++ b/odbc-api/src/buffers/text_column.rs @@ -1,6 +1,7 @@ use crate::{ + error::TooLargeBufferSize, handles::{CData, CDataMut, HasDataType}, - DataType, Error, + DataType, }; use super::{ColumnBuffer, ColumnProjections, Indicator}; @@ -41,7 +42,7 @@ impl TextColumn { /// This will allocate a value and indicator buffer for `batch_size` elements. Each value may /// have a maximum length of `max_str_len`. This implies that `max_str_len` is increased by /// one in order to make space for the null terminating zero at the end of strings. - pub fn new(batch_size: usize, max_str_len: usize) -> Result + pub fn new(batch_size: usize, max_str_len: usize) -> Result where C: Default + Copy, { @@ -55,7 +56,7 @@ impl TextColumn { let mut values = Vec::new(); values .try_reserve_exact(len) - .map_err(|_| Error::TooLargeColumnBufferSize { + .map_err(|_| TooLargeBufferSize { num_elements: batch_size, // We want the element size in bytes element_size: element_size * size_of::(), diff --git a/odbc-api/src/error.rs b/odbc-api/src/error.rs index 253d2b13..f03f83d5 100644 --- a/odbc-api/src/error.rs +++ b/odbc-api/src/error.rs @@ -4,6 +4,27 @@ use thiserror::Error as ThisError; use crate::handles::{log_diagnostics, AsHandle, Record as DiagnosticRecord, SqlResult}; +/// Error indicating a failed allocation for a column buffer +#[derive(Debug)] +pub struct TooLargeBufferSize { + /// Number of elements supposed to be in the buffer. + pub num_elements: usize, + /// Element size in the buffer in bytes. + pub element_size: usize, +} + +impl TooLargeBufferSize { + /// Map the column allocation error to an [`odbc_api::Error`] adding the context of which column + /// caused the allocation error. + pub fn add_context(self, buffer_index: u16) -> Error { + Error::TooLargeColumnBufferSize { + buffer_index, + num_elements: self.num_elements, + element_size: self.element_size, + } + } +} + #[derive(Debug, ThisError)] /// Error type used to indicate a low level ODBC call returned with SQL_ERROR. pub enum Error { @@ -82,6 +103,9 @@ pub enum Error { {element_size}." )] TooLargeColumnBufferSize { + /// Zero based column buffer index. Note that this is different from the 1 based column + /// index. + buffer_index: u16, num_elements: usize, element_size: usize, }, diff --git a/odbc-api/src/lib.rs b/odbc-api/src/lib.rs index 584e8628..26ad0d93 100644 --- a/odbc-api/src/lib.rs +++ b/odbc-api/src/lib.rs @@ -31,7 +31,7 @@ pub use self::{ cursor::{Cursor, CursorImpl, CursorRow, RowSetBuffer, RowSetCursor}, driver_complete_option::DriverCompleteOption, environment::{DataSourceInfo, DriverInfo, Environment}, - error::Error, + error::{Error, TooLargeBufferSize}, fixed_sized::Bit, handles::{ColumnDescription, DataType, Nullability}, into_parameter::IntoParameter, diff --git a/odbcsv/Cargo.toml b/odbcsv/Cargo.toml index 41c42b54..97f0c549 100644 --- a/odbcsv/Cargo.toml +++ b/odbcsv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "odbcsv" -version = "0.3.67" +version = "0.3.68" authors = ["Markus Klein"] edition = "2021" license = "MIT" @@ -29,7 +29,7 @@ readme = "Readme.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -odbc-api = { version = "0.37.0", path = "../odbc-api" } +odbc-api = { version = "0.38.0", path = "../odbc-api" } csv = "1.1.6" anyhow = "1.0.57" stderrlog = "0.5.1" diff --git a/odbcsv/Changelog.md b/odbcsv/Changelog.md index 771f2024..e35f9700 100644 --- a/odbcsv/Changelog.md +++ b/odbcsv/Changelog.md @@ -1,5 +1,9 @@ # Changelog +## 0.3.68 + +* Update dependencies + ## 0.3.67 * Do not panic if allocation of column buffers fails. Gracefully abort instead, freeing allocated resources.