use lazy_static::lazy_static;
use odbc_api::{
buffers,
buffers::TextColumn,
handles::{CDataMut, Statement},
Connection, Cursor, Environment, Error, RowSetBuffer, U16Str,
};
lazy_static! {
pub static ref ENV: Environment = {
let _ = env_logger::builder().is_test(true).try_init();
Environment::new().unwrap()
};
}
#[derive(Clone, Copy, Debug)]
pub struct Profile {
pub connection_string: &'static str,
pub index_type: &'static str,
pub blob_type: &'static str,
}
impl Profile {
pub fn connection(&self) -> Result<Connection<'static>, Error> {
ENV.connect_with_connection_string(self.connection_string)
}
pub fn setup_empty_table(
&self,
table_name: &str,
column_types: &[&str],
) -> Result<Connection<'static>, odbc_api::Error> {
let conn = self.connection()?;
setup_empty_table(&conn, self.index_type, table_name, column_types)?;
Ok(conn)
}
}
pub fn setup_empty_table(
conn: &Connection<'_>,
index_type: &str,
table_name: &str,
column_types: &[&str],
) -> Result<(), odbc_api::Error> {
let drop_table = &format!("DROP TABLE IF EXISTS {}", table_name);
let column_names = &["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"];
let cols = column_types
.iter()
.zip(column_names)
.map(|(ty, name)| format!("{} {}", name, ty))
.collect::<Vec<_>>()
.join(", ");
let create_table = format!("CREATE TABLE {} (id {},{});", table_name, index_type, cols);
conn.execute(drop_table, ())?;
conn.execute(&create_table, ())?;
Ok(())
}
pub fn table_to_string(conn: &Connection<'_>, table_name: &str, column_names: &[&str]) -> String {
let cols = column_names.join(", ");
let query = format!("SELECT {} FROM {}", cols, table_name);
let cursor = conn.execute(&query, ()).unwrap().unwrap();
cursor_to_string(cursor)
}
pub fn cursor_to_string(cursor: impl Cursor) -> String {
let batch_size = 20;
let mut buffer = buffers::TextRowSet::for_cursor(batch_size, &cursor, Some(8192)).unwrap();
let mut row_set_cursor = cursor.bind_buffer(&mut buffer).unwrap();
let mut text = String::new();
while let Some(row_set) = row_set_cursor.fetch().unwrap() {
for row_index in 0..row_set.num_rows() {
if row_index != 0 {
text.push('\n');
}
for col_index in 0..row_set.num_cols() {
if col_index != 0 {
text.push(',');
}
text.push_str(
row_set
.at_as_str(col_index, row_index)
.unwrap()
.unwrap_or("NULL"),
);
}
}
}
text
}
pub struct SingleColumnRowSetBuffer<C> {
num_rows_fetched: Box<usize>,
batch_size: usize,
column: C,
}
impl SingleColumnRowSetBuffer<TextColumn<u16>> {
pub fn with_wide_text_column(batch_size: usize, max_str_len: usize) -> Self {
Self {
num_rows_fetched: Box::new(0),
batch_size,
column: TextColumn::try_new(batch_size as usize, max_str_len).unwrap(),
}
}
pub fn ustr_at(&self, index: usize) -> Option<&U16Str> {
if index >= *self.num_rows_fetched {
panic!("Out of bounds access. In SingleColumnRowSetBuffer")
}
unsafe { self.column.ustr_at(index) }
}
}
impl SingleColumnRowSetBuffer<TextColumn<u8>> {
pub fn with_text_column(batch_size: usize, max_str_len: usize) -> Self {
Self {
num_rows_fetched: Box::new(0),
batch_size,
column: TextColumn::try_new(batch_size as usize, max_str_len).unwrap(),
}
}
pub fn value_at(&self, index: usize) -> Option<&[u8]> {
if index >= *self.num_rows_fetched {
panic!("Out of bounds access. In SingleColumnRowSetBuffer")
}
self.column.value_at(index)
}
}
impl<T> SingleColumnRowSetBuffer<Vec<T>>
where
T: Clone + Default,
{
pub fn new(batch_size: usize) -> Self {
SingleColumnRowSetBuffer {
num_rows_fetched: Box::new(0),
batch_size,
column: vec![T::default(); batch_size as usize],
}
}
pub fn get(&self) -> &[T] {
&self.column[0..*self.num_rows_fetched]
}
}
unsafe impl<C> RowSetBuffer for SingleColumnRowSetBuffer<C>
where
C: CDataMut,
{
fn bind_type(&self) -> usize {
0 }
fn row_array_size(&self) -> usize {
self.batch_size
}
fn mut_num_fetch_rows(&mut self) -> &mut usize {
self.num_rows_fetched.as_mut()
}
unsafe fn bind_to_cursor(&mut self, cursor: &mut impl Cursor) -> Result<(), odbc_api::Error> {
cursor
.stmt_mut()
.bind_col(1, &mut self.column)
.into_result(cursor.stmt_mut())?;
Ok(())
}
}