Skip to content

Commit

Permalink
direct access to buffers with nullable slices.
Browse files Browse the repository at this point in the history
  • Loading branch information
pacman82 committed Feb 20, 2022
1 parent 97d6a9e commit f8b2b23
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
31 changes: 31 additions & 0 deletions odbc-api/src/buffers/column_with_indicator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,37 @@ impl<'a, T> NullableSlice<'a, T> {
pub fn len(&self) -> usize {
self.values.len()
}

/// Read access to the underlying raw value and indicator buffer.
///
/// The number of elements in the buffer is equal to the number of rows returned in the current
/// result set. Yet the content of any value, those associated value in the indicator buffer is
/// [`crate::sys::NULL_DATA`] is undefined.
///
/// This method is useful for writing performant bindings to datastructures with similar binary
/// layout, as it allows for using memcopy rather than iterating over individual values.
///
/// # Example
///
/// ```
/// use odbc_api::{buffers::NullableSlice, sys::NULL_DATA};
///
/// // Memcopy the values out of the buffer, and make a mask of bools indicating the NULL
/// // values.
/// fn copy_values_and_make_mask(odbc_slice: NullableSlice<i32>) -> (Vec<i32>, Vec<bool>) {
/// let (values, indicators) = odbc_slice.raw_values();
/// let values = values.to_vec();
/// // Create array of bools indicating null values.
/// let mask: Vec<bool> = indicators
/// .iter()
/// .map(|&indicator| indicator != NULL_DATA)
/// .collect();
/// (values, mask)
/// }
/// ```
pub fn raw_values(&self) -> (&'a [T], &'a [isize]) {
(self.values, self.indicators)
}
}

impl<'a, T> Iterator for NullableSlice<'a, T> {
Expand Down
54 changes: 54 additions & 0 deletions odbc-api/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3046,6 +3046,60 @@ fn panic_in_drop_handlers_should_not_mask_original_error(profile: &Profile) {
panic!("original error")
}

/// Arrow uses the same binary format for the values of nullable slices, though null are represented
/// as bitmask. Make it possible for bindings to efficiently copy the values array out of a
/// nullable slice.
#[test_case(MSSQL; "Microsoft SQL Server")]
#[test_case(MARIADB; "Maria DB")]
#[test_case(SQLITE_3; "SQLite 3")]
fn memcopy_values_from_nullable_slice(profile: &Profile) {
// Given
let table_name = "MemcopyValuesFromNullableSlice";
let conn = profile
.setup_empty_table(table_name, &["INTEGER"])
.unwrap();
conn.execute(
&format!("INSERT INTO {table_name} (a) VALUES (42), (NULL), (5);"),
(),
)
.unwrap();

// When
let cursor = conn
.execute(&format!("SELECT a FROM {table_name}"), ())
.unwrap() // Unwrap Result
.unwrap(); // Unwrap Option, we know a select statement to produce a cursor.
let buffer = buffer_from_description(
3,
iter::once(BufferDescription {
kind: BufferKind::I32,
nullable: true,
}),
);
let mut cursor = cursor.bind_buffer(buffer).unwrap();
let batch = cursor.fetch().unwrap().unwrap();
let nullable_slice = match batch.column(0) {
AnyColumnView::NullableI32(nullable_slice) => nullable_slice,
_ => panic!("Expected View type to be a nullable i32"),
};
let (values, indicators) = nullable_slice.raw_values();
// Memcopy values.
let values = values.to_vec();
// Create array of bools indicating null values.
let nulls: Vec<bool> = indicators
.iter()
.map(|&indicator| indicator == sys::NULL_DATA)
.collect();

// Then
assert!(!nulls[0]);
assert_eq!(values[0], 42);
assert!(nulls[1]);
// We explicitly don't give any guarantees about the value of #values[1]`.
assert!(!nulls[2]);
assert_eq!(values[2], 5);
}

/// This test is inspired by a bug caused from a fetch statement generating a lot of diagnostic
/// messages.
#[test]
Expand Down

0 comments on commit f8b2b23

Please sign in to comment.