diff --git a/odbc-api/src/buffers/column_with_indicator.rs b/odbc-api/src/buffers/column_with_indicator.rs index c00ce6b9..622f2046 100644 --- a/odbc-api/src/buffers/column_with_indicator.rs +++ b/odbc-api/src/buffers/column_with_indicator.rs @@ -229,6 +229,37 @@ impl<'a, T> NullableSliceMut<'a, T> { pub fn len(&self) -> usize { self.values.len() } + + /// Write access to the underlying raw value and indicator buffer. + /// + /// The number of elements in the buffer is equal to `len`. + /// + /// 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 into the buffer, and set indicators according to mask + /// // values. + /// fn copy_values_and_make_mask(new_values: &[i32], mask: &[bool], odbc_slice: &mut NullableSliceMut) { + /// let (values, indicators) = odbc_slice.raw_values(); + /// values.copy_from_slice(new_values); + /// // Create array of bools indicating null values. + /// indicators.iter_mut().zip(mask.iter()).for_each(|(indicator, &mask)| { + /// *indicator = if mask { + /// 1 + /// } else { + /// NULL_DATA + /// } + /// }); + /// } + /// ``` + pub fn raw_values(&mut self) -> (&mut [T], &mut [isize]) { + (&mut self.values, &mut self.indicators) + } } impl<'a, T> NullableSliceMut<'a, T> { diff --git a/odbc-api/tests/integration.rs b/odbc-api/tests/integration.rs index c8b3f7b3..d8143dfe 100644 --- a/odbc-api/tests/integration.rs +++ b/odbc-api/tests/integration.rs @@ -1,6 +1,7 @@ mod common; use odbc_sys::{SqlDataType, Timestamp}; +use sys::NULL_DATA; use tempfile::NamedTempFile; use test_case::test_case; @@ -769,6 +770,51 @@ fn columnar_insert_timestamp(profile: &Profile) { assert_eq!(expected, actual); } +/// Insert values into a i32 column using a columnar buffer's raw values +#[test_case(MSSQL; "Microsoft SQL Server")] +// #[test_case(MARIADB; "Maria DB")] No DATEIME2 type +// #[test_case(SQLITE_3; "SQLite 3")] default precision of 3 instead 7 +fn columnar_insert_int_raw(profile: &Profile) { + let table_name = "Int"; + // Setup + let conn = profile.setup_empty_table(table_name, &["INT"]).unwrap(); + + // Fill buffer with values + let desc = BufferDescription { + kind: BufferKind::I32, + nullable: true, + }; + let mut buffer = buffer_from_description(10, iter::once(desc)); + + // Input values to insert. + let input_values = [1, 0, 3]; + let mask = [true, false, true]; + + buffer.set_num_rows(input_values.len()); + if let AnyColumnViewMut::NullableI32(mut writer) = buffer.column_mut(0) { + let (values, indicators) = writer.raw_values(); + values.copy_from_slice(&input_values); + indicators + .iter_mut() + .zip(mask.iter()) + .for_each(|(indicator, &mask)| *indicator = if mask { 1 } else { NULL_DATA }) + } else { + panic!("Expected i32 column writer"); + }; + + // Bind buffer and insert values. + conn.execute( + &format!("INSERT INTO {} (a) VALUES (?)", table_name), + &buffer, + ) + .unwrap(); + + // Query values and compare with expectation + let actual = table_to_string(&conn, table_name, &["a"]); + let expected = "1\nNULL\n3"; + assert_eq!(expected, actual); +} + /// Insert values into a DATETIME2(3) column using a columnar buffer. Milliseconds precision is /// different from the default precision 7 (100ns). #[test_case(MSSQL; "Microsoft SQL Server")]