1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use paste::paste;
use std::{ffi::CStr, ops::Deref, sync::Arc};
use time::{Date, Duration, PrimitiveDateTime, Time};

use crate::{
    connection::ConnectionHandle,
    ffi,
    value::{
        date_to_duckdb_date, datetime_to_duckdb_timestamp, i128_to_duckdb_hugeint,
        time_to_duckdb_time,
    },
};

pub struct AppenderHandle {
    handle: ffi::duckdb_appender,
    _parent: Arc<ConnectionHandle>,
}

impl AppenderHandle {
    pub fn create(
        connection: Arc<ConnectionHandle>,
        schema: Option<&CStr>,
        table: &CStr,
    ) -> Result<Self, String> {
        unsafe {
            let mut out_appender: ffi::duckdb_appender = std::mem::zeroed();
            let r = ffi::duckdb_appender_create(
                **connection,
                schema.map_or(std::ptr::null(), |s| s.as_ptr()),
                table.as_ptr(),
                &mut out_appender,
            );
            if r != ffi::DuckDBSuccess {
                let err = CStr::from_ptr(ffi::duckdb_appender_error(out_appender));
                let err = err.to_string_lossy().into_owned();
                ffi::duckdb_appender_destroy(&mut out_appender);
                Err(err)
            } else {
                Ok(Self::from_raw(out_appender, connection))
            }
        }
    }
    pub unsafe fn from_raw(raw: ffi::duckdb_appender, connection: Arc<ConnectionHandle>) -> Self {
        Self {
            handle: raw,
            _parent: connection,
        }
    }
    pub fn error(&self) -> String {
        let err = unsafe { CStr::from_ptr(ffi::duckdb_appender_error(**self)) };
        err.to_string_lossy().into_owned()
    }
    pub fn flush(&self) -> Result<(), String> {
        self.do_or_error(unsafe { ffi::duckdb_appender_flush(**self) })
    }
    pub fn close(&self) -> Result<(), String> {
        self.do_or_error(unsafe { ffi::duckdb_appender_close(**self) })
    }
    fn do_or_error(&self, state: ffi::duckdb_state) -> Result<(), String> {
        if state != ffi::DuckDBSuccess {
            Err(self.error())
        } else {
            Ok(())
        }
    }
}

macro_rules! fn_append {
    ($ty:ty, $duck_ty:ty) => {
        paste! {
            fn_append!{$ty, [<append_ $duck_ty>], ffi::[<duckdb_append_ $duck_ty>]}
        }
    };
    ($ty:ty, $method:ident, $db_method: expr) => {
        pub fn $method(&self, value: $ty) -> Result<(), String> {
            self.do_or_error(unsafe { $db_method(**self, value) })
        }
    };
}

impl AppenderHandle {
    pub fn begin_row(&self) -> Result<(), String> {
        self.do_or_error(unsafe { ffi::duckdb_appender_begin_row(**self) })
    }
    pub fn end_row(&self) -> Result<(), String> {
        self.do_or_error(unsafe { ffi::duckdb_appender_end_row(**self) })
    }
    fn_append! {bool, bool}
    fn_append! {i8, int8}
    fn_append! {i16, int16}
    fn_append! {i32, int32}
    fn_append! {i64, int64}
    pub fn append_hugeint(&self, value: i128) -> Result<(), String> {
        let h = i128_to_duckdb_hugeint(value);
        self.do_or_error(unsafe { ffi::duckdb_append_hugeint(**self, h) })
    }
    fn_append! {u8, uint8}
    fn_append! {u16, uint16}
    fn_append! {u32, uint32}
    fn_append! {u64, uint64}
    fn_append! {f32, float}
    fn_append! {f64, double}
    pub fn append_date(&self, value: Date) -> Result<(), String> {
        let d = date_to_duckdb_date(&value);
        self.do_or_error(unsafe { ffi::duckdb_append_date(**self, ffi::duckdb_to_date(d)) })
    }
    pub fn append_time(&self, value: Time) -> Result<(), String> {
        let t = time_to_duckdb_time(&value);
        self.do_or_error(unsafe { ffi::duckdb_append_time(**self, ffi::duckdb_to_time(t)) })
    }
    pub fn append_timestamp(&self, value: PrimitiveDateTime) -> Result<(), String> {
        let dt = datetime_to_duckdb_timestamp(&value);
        self.do_or_error(unsafe {
            ffi::duckdb_append_timestamp(**self, ffi::duckdb_to_timestamp(dt))
        })
    }
    pub fn append_interval(&self, value: Duration) -> Result<(), String> {
        todo!()
    }
    pub fn append_varchar(&self, value: &CStr) -> Result<(), String> {
        self.do_or_error(unsafe { ffi::duckdb_append_varchar(**self, value.as_ptr()) })
    }
    pub fn append_varchar_length(&self, value: &str) -> Result<(), String> {
        self.do_or_error(unsafe {
            let b = value.as_bytes();
            ffi::duckdb_append_varchar_length(**self, b.as_ptr() as *const _, b.len() as u64)
        })
    }
    pub fn append_blob(&self, value: &[u8]) -> Result<(), String> {
        self.do_or_error(unsafe {
            ffi::duckdb_append_blob(**self, value.as_ptr() as *const _, value.len() as u64)
        })
    }
    pub fn append_null(&self) -> Result<(), String> {
        self.do_or_error(unsafe { ffi::duckdb_append_null(**self) })
    }
}

impl Deref for AppenderHandle {
    type Target = ffi::duckdb_appender;

    fn deref(&self) -> &Self::Target {
        &self.handle
    }
}

impl Drop for AppenderHandle {
    fn drop(&mut self) {
        unsafe {
            if ffi::duckdb_appender_destroy(&mut self.handle) != ffi::DuckDBSuccess {
                panic!("duckdb_appender_destroy() failed");
            }
        }
    }
}