Skip to content

Commit

Permalink
add get_binary
Browse files Browse the repository at this point in the history
  • Loading branch information
pacman82 committed Apr 27, 2021
1 parent 9468351 commit ab2be17
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 24 deletions.
57 changes: 56 additions & 1 deletion odbc-api/src/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
buffers::Indicator,
handles::{Statement, StatementImpl},
parameter::VarCharSliceMut,
parameter::{VarBinarySliceMut, VarCharSliceMut},
ColumnDescription, DataType, Error, Output,
};

Expand Down Expand Up @@ -212,6 +212,61 @@ where
};
Ok(not_null)
}

/// Retrieves arbitrary large binary data from the row and stores it in the buffer. Column index
/// starts at `1`.
///
/// # Return
///
/// `true` indicates that the value has not been `NULL` and the value has been placed in `buf`.
/// `false` indicates that the value is `NULL`. The buffer is cleared in that case.
pub fn get_binary(&mut self, col_or_param_num: u16, buf: &mut Vec<u8>) -> Result<bool, Error> {
// Utilize all of the allocated buffer. Make sure buffer can at least hold one element.
buf.resize(max(1, buf.capacity()), 0);
// We repeatedly fetch data and add it to the buffer. The buffer length is therefore the
// accumulated value size. This variable keeps track of the number of bytes we added with
// the current call to get_data.
let mut fetch_size = buf.len();
let mut target = VarBinarySliceMut::from_buffer(buf.as_mut_slice(), Indicator::Null);
// Fetch binary data into buffer.
self.get_data(col_or_param_num, &mut target)?;
let not_null = loop {
match target.indicator() {
// Value is `NULL`. We are done here.
Indicator::Null => {
buf.clear();
break false;
}
// We do not know how large the value is. Let's fetch the data with repeated calls
// to get_data.
Indicator::NoTotal => {
let old_len = buf.len();
// Use an exponential strategy for increasing buffer size.
buf.resize(old_len * 2, 0);
target = VarBinarySliceMut::from_buffer(&mut buf[old_len..], Indicator::Null);
self.get_data(col_or_param_num, &mut target)?;
}
// We did get the complete value, including the terminating zero. Let's resize the
// buffer to match the retrieved value exactly (excluding terminating zero).
Indicator::Length(len) if len <= fetch_size => {
let shrink_by = fetch_size - len;
buf.resize(buf.len() - shrink_by, 0);
break true;
}
// We did not get all of the value in one go, but the data source has been friendly
// enough to tell us how much is missing.
Indicator::Length(len) => {
let still_missing = len - fetch_size;
fetch_size = still_missing;
let old_len = buf.len();
buf.resize(old_len + still_missing, 0);
target = VarBinarySliceMut::from_buffer(&mut buf[old_len..], Indicator::Null);
self.get_data(col_or_param_num, &mut target)?;
}
}
};
Ok(not_null)
}
}

/// An iterator calling `col_name` for each column_name and converting the result into UTF-8. See
Expand Down
6 changes: 5 additions & 1 deletion odbc-api/src/into_parameter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{InputParameter, Nullable, fixed_sized::Pod, parameter::{VarBinaryBox, VarBinarySlice, VarCharBox, VarCharSlice}};
use crate::{
fixed_sized::Pod,
parameter::{VarBinaryBox, VarBinarySlice, VarCharBox, VarCharSlice},
InputParameter, Nullable,
};

/// An instance can be consumed and to create a parameter which can be bound to a statement during
/// execution.
Expand Down
4 changes: 2 additions & 2 deletions odbc-api/src/parameter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,12 @@
//! implemented entirely in safe code, and is a suitable spot to enable support for your custom
//! types.
mod c_string;
mod varchar;
mod varbin;
mod varchar;

pub use self::{
varbin::{VarBinary, VarBinaryArray, VarBinaryBox, VarBinarySlice, VarBinarySliceMut},
varchar::{VarChar, VarCharArray, VarCharBox, VarCharSlice, VarCharSliceMut}
varchar::{VarChar, VarCharArray, VarCharBox, VarCharSlice, VarCharSliceMut},
};

use std::ffi::c_void;
Expand Down
87 changes: 77 additions & 10 deletions odbc-api/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use odbc_api::{
AnyColumnView, AnyColumnViewMut, BufferDescription, BufferKind, ColumnarRowSet, Indicator,
TextRowSet,
},
parameter::{VarCharArray, VarCharSlice, VarBinaryArray},
parameter::{VarBinaryArray, VarCharArray, VarCharSlice},
ColumnDescription, Cursor, DataType, InputParameter, IntoParameter, Nullability, Nullable,
U16String,
};
Expand Down Expand Up @@ -1543,18 +1543,15 @@ fn parameter_option_byte_slice(profile: &Profile) {
let mut prepared = conn.prepare(&sql).unwrap();
prepared.execute(&None::<&[u8]>.into_parameter()).unwrap();
prepared
.execute(&Some(&[1,2,3][..]).into_parameter())
.execute(&Some(&[1, 2, 3][..]).into_parameter())
.unwrap();
prepared.execute(&None::<&[u8]>.into_parameter()).unwrap();
prepared
.execute(&Some(vec![1,2,3]).into_parameter())
.execute(&Some(vec![1, 2, 3]).into_parameter())
.unwrap();

let cursor = conn
.execute(
&format!("SELECT a FROM {} ORDER BY id", table_name),
(),
)
.execute(&format!("SELECT a FROM {} ORDER BY id", table_name), ())
.unwrap()
.unwrap();
let actual = cursor_to_string(cursor);
Expand Down Expand Up @@ -1598,7 +1595,7 @@ fn parameter_varbinary_512(profile: &Profile) {

prepared.execute(&VarBinaryArray::<512>::NULL).unwrap();
prepared
.execute(&VarBinaryArray::<512>::new(&[1,2,3]))
.execute(&VarBinaryArray::<512>::new(&[1, 2, 3]))
.unwrap();

let actual = table_to_string(&conn, table_name, &["a"]);
Expand Down Expand Up @@ -1869,7 +1866,10 @@ fn get_data_string(profile: &Profile) {
setup_empty_table(&conn, profile.index_type, table_name, &["Varchar(50)"]).unwrap();

conn.execute(
&format!("INSERT INTO {} (a) VALUES ('Hello, World!'), (NULL)", table_name),
&format!(
"INSERT INTO {} (a) VALUES ('Hello, World!'), (NULL)",
table_name
),
(),
)
.unwrap();
Expand Down Expand Up @@ -1921,7 +1921,7 @@ fn get_data_binary(profile: &Profile) {
let mut actual = VarBinaryArray::<32>::NULL;

row.get_data(1, &mut actual).unwrap();
assert_eq!(Some(&[1u8,2,3][..]), actual.as_bytes());
assert_eq!(Some(&[1u8, 2, 3][..]), actual.as_bytes());

// second row
row = cursor.next_row().unwrap().unwrap();
Expand Down Expand Up @@ -2048,6 +2048,73 @@ fn short_strings_get_text(profile: &Profile) {
assert_eq!("Hello, World!", std::str::from_utf8(&actual).unwrap());
}

/// Retrieving of short binary values using get_data. This also helps to assert that we correctly
/// shorten the vectors length if the capacity of the originally passed in vector had been larger
/// than the retrieved payload.
#[test_case(MSSQL; "Microsoft SQL Server")]
#[test_case(MARIADB; "Maria DB")]
#[test_case(SQLITE_3; "SQLite 3")]
fn short_get_binary(profile: &Profile) {
let table_name = "ShortGetBinary";
let conn = ENV
.connect_with_connection_string(profile.connection_string)
.unwrap();
setup_empty_table(&conn, profile.index_type, table_name, &["Varbinary(15)"]).unwrap();

conn.execute(
&format!("INSERT INTO {} (a) VALUES (?)", table_name),
&[1u8, 2, 3].into_parameter(),
)
.unwrap();

let mut cursor = conn
.execute(&format!("SELECT a FROM {} ORDER BY id", table_name), ())
.unwrap()
.unwrap();

let mut row = cursor.next_row().unwrap().unwrap();

// Make initial buffer larger than the string we want to fetch.
let mut actual = Vec::with_capacity(100);

row.get_binary(1, &mut actual).unwrap();

assert_eq!(&[1u8, 2, 3][..], &actual);
}

/// Test insertion and retrieving of values larger than the initially provided buffer using
/// get_binary.
#[test_case(MSSQL; "Microsoft SQL Server")]
// #[test_case(MARIADB; "Maria DB")] Does not support Varchar(max) syntax
// #[test_case(SQLITE_3; "SQLite 3")] Does not support Varchar(max) syntax
fn large_get_binary(profile: &Profile) {
let table_name = "LargeGetBinary";
let conn = ENV
.connect_with_connection_string(profile.connection_string)
.unwrap();
setup_empty_table(&conn, profile.index_type, table_name, &["Varbinary(max)"]).unwrap();

let input = vec![42; 2000];

conn.execute(
&format!("INSERT INTO {} (a) VALUES (?)", table_name),
&input.as_slice().into_parameter(),
)
.unwrap();

let mut cursor = conn
.execute(&format!("SELECT a FROM {} ORDER BY id", table_name), ())
.unwrap()
.unwrap();

let mut row = cursor.next_row().unwrap().unwrap();
let mut actual = Vec::new();

row.get_binary(1, &mut actual).unwrap();

assert_eq!(input, actual);
}

/// Demonstrates applying an upper limit to a text buffer and detecting truncation.
#[test_case(MSSQL; "Microsoft SQL Server")]
#[test_case(MARIADB; "Maria DB")]
Expand Down
14 changes: 4 additions & 10 deletions odbcsv/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use anyhow::{bail, Error};
use log::info;
use odbc_api::{
buffers::TextRowSet, escape_attribute_value, Connection, Cursor,
Environment, IntoParameter,
buffers::TextRowSet, escape_attribute_value, Connection, Cursor, Environment, IntoParameter,
};
use std::{
fs::File,
Expand All @@ -11,13 +10,13 @@ use std::{
};
use structopt::StructOpt;

#[cfg(target_os = "windows")]
use odbc_api::DriverCompleteOption;
#[cfg(target_os = "windows")]
use winit::{
event_loop::EventLoop,
window::{Window, WindowBuilder},
};
#[cfg(target_os = "windows")]
use odbc_api::DriverCompleteOption;

/// Query an ODBC data source and output the result as CSV.
#[derive(StructOpt)]
Expand Down Expand Up @@ -180,7 +179,6 @@ fn open_connection<'e>(
environment: &'e Environment,
opt: &ConnectOpts,
) -> Result<Connection<'e>, odbc_api::Error> {

if let Some(dsn) = opt.dsn.as_deref() {
return environment.connect(
dsn,
Expand All @@ -201,11 +199,7 @@ fn open_connection<'e>(
#[cfg(target_os = "windows")]
if opt.prompt {
let window = message_only_window().unwrap();
return environment.driver_connect(
&cs,
None,
DriverCompleteOption::Complete(&window),
);
return environment.driver_connect(&cs, None, DriverCompleteOption::Complete(&window));
}

// Would rather use conditional compilation on the flag itself. While this works fine, it does
Expand Down

0 comments on commit ab2be17

Please sign in to comment.