use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::path::Path;
use std::ptr;
use std::sync::Once;
use crate::error::{Error, Result};
use crate::ffi;
use crate::program::ProgramBuilder;
static SQLITE_INIT: Once = Once::new();
fn ensure_sqlite_initialized() {
SQLITE_INIT.call_once(|| unsafe {
ffi::sqlite3_initialize();
});
}
pub struct Connection {
raw: *mut ffi::sqlite3,
_marker: PhantomData<*const ()>,
}
impl Connection {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_str = path.as_ref().to_str().ok_or(Error::InvalidPath)?;
Self::open_with_flags(
path_str,
ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE,
)
}
pub fn open_in_memory() -> Result<Self> {
Self::open(":memory:")
}
pub fn open_with_flags(path: &str, flags: i32) -> Result<Self> {
ensure_sqlite_initialized();
let c_path = CString::new(path)?;
let mut db: *mut ffi::sqlite3 = ptr::null_mut();
let rc = unsafe { ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags, ptr::null()) };
if rc != ffi::SQLITE_OK {
let msg = if !db.is_null() {
unsafe {
let err = ffi::sqlite3_errmsg(db);
if !err.is_null() {
CStr::from_ptr(err).to_string_lossy().into_owned()
} else {
String::new()
}
}
} else {
String::new()
};
if !db.is_null() {
unsafe { ffi::sqlite3_close(db) };
}
return Err(Error::from_code_with_message(rc, msg));
}
Ok(Connection {
raw: db,
_marker: PhantomData,
})
}
pub fn new_program(&mut self) -> Result<ProgramBuilder> {
ProgramBuilder::new(self.raw)
}
pub fn last_error(&self) -> Option<String> {
unsafe {
let err = ffi::sqlite3_errmsg(self.raw);
if err.is_null() {
None
} else {
Some(CStr::from_ptr(err).to_string_lossy().into_owned())
}
}
}
pub fn last_error_code(&self) -> i32 {
unsafe { ffi::sqlite3_errcode(self.raw) }
}
pub unsafe fn raw_ptr(&self) -> *mut ffi::sqlite3 {
self.raw
}
}
impl Drop for Connection {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe {
ffi::sqlite3_close_v2(self.raw);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_open_in_memory() {
let conn = Connection::open_in_memory();
assert!(conn.is_ok());
}
#[test]
fn test_open_invalid_path() {
let result = Connection::open_with_flags(
"/nonexistent/path/to/db.sqlite",
ffi::SQLITE_OPEN_READONLY,
);
assert!(result.is_err());
}
}