use std::ffi::CStr;
use std::os::raw::c_void;
use libduckdb_sys::{
duckdb_client_context_get_file_system, duckdb_create_file_open_options,
duckdb_destroy_file_handle, duckdb_destroy_file_open_options, duckdb_destroy_file_system,
duckdb_file_flag, duckdb_file_flag_DUCKDB_FILE_FLAG_APPEND,
duckdb_file_flag_DUCKDB_FILE_FLAG_CREATE, duckdb_file_flag_DUCKDB_FILE_FLAG_CREATE_NEW,
duckdb_file_flag_DUCKDB_FILE_FLAG_READ, duckdb_file_flag_DUCKDB_FILE_FLAG_WRITE,
duckdb_file_handle, duckdb_file_handle_close, duckdb_file_handle_error_data,
duckdb_file_handle_read, duckdb_file_handle_seek, duckdb_file_handle_size,
duckdb_file_handle_sync, duckdb_file_handle_tell, duckdb_file_handle_write,
duckdb_file_open_options, duckdb_file_open_options_set_flag, duckdb_file_system,
duckdb_file_system_error_data, duckdb_file_system_open, DuckDBSuccess,
};
use crate::client_context::ClientContext;
use crate::error_data::ErrorData;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FileFlag {
Read,
Write,
Create,
CreateNew,
Append,
}
impl FileFlag {
#[must_use]
const fn to_raw(self) -> duckdb_file_flag {
match self {
Self::Read => duckdb_file_flag_DUCKDB_FILE_FLAG_READ,
Self::Write => duckdb_file_flag_DUCKDB_FILE_FLAG_WRITE,
Self::Create => duckdb_file_flag_DUCKDB_FILE_FLAG_CREATE,
Self::CreateNew => duckdb_file_flag_DUCKDB_FILE_FLAG_CREATE_NEW,
Self::Append => duckdb_file_flag_DUCKDB_FILE_FLAG_APPEND,
}
}
}
pub struct FileOpenOptions {
options: duckdb_file_open_options,
}
impl FileOpenOptions {
#[must_use]
pub fn new() -> Self {
let options = unsafe { duckdb_create_file_open_options() };
Self { options }
}
#[must_use]
pub fn read_only() -> Self {
let opts = Self::new();
opts.set_flag(FileFlag::Read, true);
opts
}
#[must_use]
pub fn write_create() -> Self {
let opts = Self::new();
opts.set_flag(FileFlag::Write, true);
opts.set_flag(FileFlag::Create, true);
opts
}
pub fn set_flag(&self, flag: FileFlag, value: bool) -> bool {
if self.options.is_null() {
return false;
}
let state =
unsafe { duckdb_file_open_options_set_flag(self.options, flag.to_raw(), value) };
state == DuckDBSuccess
}
#[inline]
#[must_use]
pub const fn as_raw(&self) -> duckdb_file_open_options {
self.options
}
}
impl Default for FileOpenOptions {
fn default() -> Self {
Self::new()
}
}
impl Drop for FileOpenOptions {
fn drop(&mut self) {
if !self.options.is_null() {
unsafe { duckdb_destroy_file_open_options(&raw mut self.options) };
}
}
}
pub struct FileSystem {
fs: duckdb_file_system,
}
impl FileSystem {
#[must_use]
pub fn from_client_context(context: &ClientContext) -> Option<Self> {
let fs = unsafe { duckdb_client_context_get_file_system(context.as_raw()) };
if fs.is_null() {
None
} else {
Some(Self { fs })
}
}
#[inline]
#[must_use]
pub const unsafe fn from_raw(fs: duckdb_file_system) -> Self {
Self { fs }
}
#[inline]
#[must_use]
pub const fn as_raw(&self) -> duckdb_file_system {
self.fs
}
pub fn open(&self, path: &CStr, options: &FileOpenOptions) -> Result<FileHandle, ErrorData> {
let mut handle: duckdb_file_handle = std::ptr::null_mut();
let state = unsafe {
duckdb_file_system_open(self.fs, path.as_ptr(), options.as_raw(), &raw mut handle)
};
if state == DuckDBSuccess && !handle.is_null() {
Ok(unsafe { FileHandle::from_raw(handle) })
} else {
Err(self.error_data())
}
}
#[must_use]
pub fn error_data(&self) -> ErrorData {
let raw = unsafe { duckdb_file_system_error_data(self.fs) };
unsafe { ErrorData::from_raw(raw) }
}
}
impl Drop for FileSystem {
fn drop(&mut self) {
if !self.fs.is_null() {
unsafe { duckdb_destroy_file_system(&raw mut self.fs) };
}
}
}
pub struct FileHandle {
handle: duckdb_file_handle,
}
impl FileHandle {
#[inline]
#[must_use]
pub const unsafe fn from_raw(handle: duckdb_file_handle) -> Self {
Self { handle }
}
#[inline]
#[must_use]
pub const fn as_raw(&self) -> duckdb_file_handle {
self.handle
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize, ErrorData> {
let size = i64::try_from(buf.len()).unwrap_or(i64::MAX);
let n = unsafe {
duckdb_file_handle_read(self.handle, buf.as_mut_ptr().cast::<c_void>(), size)
};
if n < 0 {
Err(self.error_data())
} else {
Ok(usize::try_from(n).unwrap_or(0))
}
}
pub fn write(&self, buf: &[u8]) -> Result<usize, ErrorData> {
let size = i64::try_from(buf.len()).unwrap_or(i64::MAX);
let n =
unsafe { duckdb_file_handle_write(self.handle, buf.as_ptr().cast::<c_void>(), size) };
if n < 0 {
Err(self.error_data())
} else {
Ok(usize::try_from(n).unwrap_or(0))
}
}
pub fn seek(&self, position: u64) -> Result<(), ErrorData> {
let pos = i64::try_from(position).unwrap_or(i64::MAX);
let state = unsafe { duckdb_file_handle_seek(self.handle, pos) };
self.check(state)
}
#[must_use]
pub fn tell(&self) -> i64 {
unsafe { duckdb_file_handle_tell(self.handle) }
}
#[must_use]
pub fn size(&self) -> i64 {
unsafe { duckdb_file_handle_size(self.handle) }
}
pub fn sync(&self) -> Result<(), ErrorData> {
let state = unsafe { duckdb_file_handle_sync(self.handle) };
self.check(state)
}
pub fn close(&self) -> Result<(), ErrorData> {
let state = unsafe { duckdb_file_handle_close(self.handle) };
self.check(state)
}
#[must_use]
pub fn error_data(&self) -> ErrorData {
let raw = unsafe { duckdb_file_handle_error_data(self.handle) };
unsafe { ErrorData::from_raw(raw) }
}
fn check(&self, state: libduckdb_sys::duckdb_state) -> Result<(), ErrorData> {
if state == DuckDBSuccess {
Ok(())
} else {
Err(self.error_data())
}
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe { duckdb_destroy_file_handle(&raw mut self.handle) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_flag_distinct_raw_values() {
let flags = [
FileFlag::Read,
FileFlag::Write,
FileFlag::Create,
FileFlag::CreateNew,
FileFlag::Append,
];
for (i, a) in flags.iter().enumerate() {
for b in flags.iter().skip(i + 1) {
assert_ne!(a.to_raw(), b.to_raw(), "{a:?} and {b:?} share a raw value");
}
}
}
}