mdbsql 0.1.1

SQL query for Access database on Unix-like systems
use std::ffi::c_char;
use std::ffi::{CStr, CString};
use std::os::unix::ffi::OsStrExt;
use std::path::Path;

use crate::error::Error;

#[allow(clippy::all)]
#[allow(non_upper_case_globals)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
#[allow(dead_code)]
mod bindings {
    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
use bindings::*;

#[derive(Debug)]
pub struct Value(*const c_char);

impl Value {
    pub fn get(&self) -> Result<&str, Error> {
        unsafe { Ok(CStr::from_ptr(self.0).to_str()?) }
    }
}

#[derive(Debug)]
pub struct Column(*const MdbSQLColumn);

impl Column {
    pub fn name(&self) -> String {
        unsafe { CStr::from_ptr((*self.0).name).to_str().unwrap().to_string() }
    }
}

pub struct Mdb(*mut MdbSQL);

unsafe impl Send for Mdb {}

impl Drop for Mdb {
    fn drop(&mut self) {
        unsafe { mdb_sql_exit(self.0) }
    }
}

impl Mdb {
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
        if !path.as_ref().is_file() {
            return Err(Error::InvalidPath);
        }

        let path = CString::new(path.as_ref().as_os_str().as_bytes())?;
        let path = path.as_ptr() as *const c_char;

        unsafe {
            let mdb_handle = mdb_open(path, MdbFileFlags_MDB_NOFLAGS);
            if mdb_handle.is_null() {
                Err(Error::InvalidMdbFile)
            } else {
                let db_ptr = mdb_sql_init();
                (*db_ptr).mdb = mdb_handle;
                Ok(Mdb(db_ptr))
            }
        }
    }

    pub fn columns(&self) -> Vec<Column> {
        unsafe {
            let columns = *(*self.0).columns;
            let columns_data = columns.pdata as *const *const MdbSQLColumn;
            (0..columns.len as isize)
                .into_iter()
                .map(|i| *columns_data.offset(i))
                .map(Column)
                .collect()
        }
    }

    pub fn bound_values(&self) -> Vec<Value> {
        unsafe {
            let values = *(*self.0).bound_values;
            let values_data = values.pdata as *const *const c_char;
            (0..values.len as isize)
                .into_iter()
                .map(|i| *values_data.offset(i))
                .map(Value)
                .collect()
        }
    }

    pub fn run_query(&self, query: *const c_char) {
        unsafe {
            mdb_sql_run_query(self.0, query);
        }
    }

    pub fn error_msg(&self) -> Option<String> {
        unsafe {
            let error_msg = (*self.0).error_msg;
            match error_msg[0] {
                0 => None,
                _ => {
                    let msg = CStr::from_ptr(error_msg.as_ptr());
                    let msg = msg.to_str().unwrap().to_string();
                    Some(msg)
                }
            }
        }
    }

    pub fn fetch_row(&self) -> bool {
        unsafe { mdb_sql_fetch_row(self.0, (*self.0).cur_table) == 1 }
    }

    pub fn reset(&self) {
        unsafe { mdb_sql_reset(self.0) }
    }
}