Skip to content

Commit 61cf4d6

Browse files
committed
Add support for DEFAULT values in Appender
Implements `AppendDefault` marker type that allows appending a column's `DEFAULT` value (as defined in table schema) when using the Appender API. Fixes #551
1 parent 1a1c690 commit 61cf4d6

File tree

7 files changed

+113
-11
lines changed

7 files changed

+113
-11
lines changed

crates/duckdb/examples/appender.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use duckdb::{params, Connection, DropBehavior, Result};
1+
use duckdb::{params, types::AppendDefault, Connection, DropBehavior, Result};
22

33
fn main() -> Result<()> {
44
//let mut db = Connection::open("10m.db")?;
@@ -10,7 +10,7 @@ fn main() -> Result<()> {
1010
id INTEGER not null, -- primary key,
1111
area CHAR(6),
1212
age TINYINT not null,
13-
active TINYINT not null
13+
active TINYINT DEFAULT 1,
1414
);";
1515
db.execute_batch(create_table_sql)?;
1616

@@ -25,12 +25,7 @@ fn main() -> Result<()> {
2525
// }
2626

2727
for i in 0..row_count {
28-
app.append_row(params![
29-
i,
30-
get_random_area_code(),
31-
get_random_age(),
32-
get_random_active(),
33-
])?;
28+
app.append_row(params![i, get_random_area_code(), get_random_age(), AppendDefault])?;
3429
}
3530
}
3631

crates/duckdb/src/appender/mod.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ impl Appender<'_> {
8383
result_from_duckdb_appender(rc, &mut self.app)
8484
}
8585

86+
/// Append a DEFAULT value to the current row
87+
#[inline]
88+
fn append_default(&mut self) -> Result<()> {
89+
let rc = unsafe { ffi::duckdb_append_default(self.app) };
90+
if rc != 0 {
91+
return Err(Error::AppendError);
92+
}
93+
Ok(())
94+
}
95+
8696
#[inline]
8797
pub(crate) fn bind_parameters<P>(&mut self, params: P) -> Result<()>
8898
where
@@ -95,13 +105,14 @@ impl Appender<'_> {
95105
Ok(())
96106
}
97107

98-
fn bind_parameter<P: ?Sized + ToSql>(&self, param: &P) -> Result<()> {
108+
fn bind_parameter<P: ?Sized + ToSql>(&mut self, param: &P) -> Result<()> {
99109
let value = param.to_sql()?;
100110

101111
let ptr = self.app;
102112
let value = match value {
103113
ToSqlOutput::Borrowed(v) => v,
104114
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
115+
ToSqlOutput::AppendDefault => return self.append_default(),
105116
};
106117
// NOTE: we ignore the return value here
107118
// because if anything failed, end_row will fail
@@ -189,7 +200,7 @@ impl fmt::Debug for Appender<'_> {
189200

190201
#[cfg(test)]
191202
mod test {
192-
use crate::{params, Connection, Error, Result};
203+
use crate::{params, types::AppendDefault, Connection, Error, Result};
193204

194205
#[test]
195206
fn test_append_one_row() -> Result<()> {
@@ -389,4 +400,50 @@ mod test {
389400

390401
Ok(())
391402
}
403+
404+
#[test]
405+
fn test_append_default() -> Result<()> {
406+
let db = Connection::open_in_memory()?;
407+
db.execute_batch(
408+
"CREATE TABLE test (
409+
id INTEGER,
410+
name VARCHAR,
411+
status VARCHAR DEFAULT 'active'
412+
)",
413+
)?;
414+
415+
{
416+
let mut app = db.appender("test")?;
417+
app.append_row(params![1, "Alice", AppendDefault])?;
418+
app.append_row(params![2, "Bob", AppendDefault])?;
419+
app.append_row(params![3, AppendDefault, AppendDefault])?;
420+
app.append_row(params![4, None::<String>, "inactive"])?;
421+
}
422+
423+
let rows: Vec<(i32, Option<String>, String)> = db
424+
.prepare("SELECT id, name, status FROM test ORDER BY id")?
425+
.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
426+
.collect::<Result<Vec<_>>>()?;
427+
428+
assert_eq!(rows.len(), 4);
429+
assert_eq!(rows[0], (1, Some("Alice".to_string()), "active".to_string()));
430+
assert_eq!(rows[1], (2, Some("Bob".to_string()), "active".to_string()));
431+
assert_eq!(rows[2], (3, None, "active".to_string()));
432+
assert_eq!(rows[3], (4, None, "inactive".to_string()));
433+
434+
Ok(())
435+
}
436+
437+
#[test]
438+
fn test_append_default_in_prepared_statement_fails() -> Result<()> {
439+
let db = Connection::open_in_memory()?;
440+
db.execute_batch("CREATE TABLE test (id INTEGER, name VARCHAR DEFAULT 'test')")?;
441+
442+
let mut stmt = db.prepare("INSERT INTO test VALUES (?, ?)")?;
443+
let result = stmt.execute(params![1, AppendDefault]);
444+
445+
assert!(matches!(result, Err(Error::ToSqlConversionFailure(_))));
446+
447+
Ok(())
448+
}
392449
}

crates/duckdb/src/pragma.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ impl Sql {
6161
let value = match value {
6262
ToSqlOutput::Borrowed(v) => v,
6363
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
64+
ToSqlOutput::AppendDefault => {
65+
return Err(Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
66+
std::io::ErrorKind::InvalidInput,
67+
"AppendDefault is only valid for Appender operations, not for pragmas",
68+
))));
69+
}
6470
};
6571
match value {
6672
ValueRef::BigInt(i) => {

crates/duckdb/src/statement.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,12 @@ impl Statement<'_> {
534534
let value = match value {
535535
ToSqlOutput::Borrowed(v) => v,
536536
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
537+
ToSqlOutput::AppendDefault => {
538+
return Err(Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
539+
std::io::ErrorKind::InvalidInput,
540+
"AppendDefault is only valid for Appender operations, not for prepared statements",
541+
))));
542+
}
537543
};
538544
// TODO: bind more
539545
let rc = match value {

crates/duckdb/src/types/chrono.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ mod test {
284284
let value = match sqled {
285285
ToSqlOutput::Borrowed(v) => v,
286286
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
287+
ToSqlOutput::AppendDefault => unreachable!(),
287288
};
288289
let reversed = FromSql::column_result(value).unwrap();
289290

crates/duckdb/src/types/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use self::{
66
from_sql::{FromSql, FromSqlError, FromSqlResult},
77
ordered_map::OrderedMap,
88
string::DuckString,
9-
to_sql::{ToSql, ToSqlOutput},
9+
to_sql::{AppendDefault, ToSql, ToSqlOutput},
1010
value::Value,
1111
value_ref::{EnumType, ListType, TimeUnit, ValueRef},
1212
};

crates/duckdb/src/types/to_sql.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@ use super::{Null, TimeUnit, Value, ValueRef};
22
use crate::Result;
33
use std::borrow::Cow;
44

5+
/// Marker type that can be used in Appender params to indicate DEFAULT value.
6+
///
7+
/// This is useful when you want to append a row with some columns using their
8+
/// default values (as defined in the table schema). Unlike `Null` which explicitly
9+
/// sets a column to NULL, `AppendDefault` uses the column's DEFAULT expression.
10+
///
11+
/// ## Example
12+
///
13+
/// ```rust,no_run
14+
/// # use duckdb::{Connection, Result, params};
15+
/// # use duckdb::types::AppendDefault;
16+
///
17+
/// fn append_with_default(conn: &Connection) -> Result<()> {
18+
/// conn.execute_batch(
19+
/// "CREATE TABLE people (id INTEGER, name VARCHAR, status VARCHAR DEFAULT 'active')"
20+
/// )?;
21+
///
22+
/// let mut app = conn.appender("people")?;
23+
/// app.append_row(params![1, "Alice", AppendDefault])?;
24+
/// Ok(())
25+
/// }
26+
/// ```
27+
#[derive(Copy, Clone, Debug)]
28+
pub struct AppendDefault;
29+
530
/// `ToSqlOutput` represents the possible output types for implementers of the
631
/// [`ToSql`] trait.
732
#[derive(Clone, Debug, PartialEq)]
@@ -12,6 +37,10 @@ pub enum ToSqlOutput<'a> {
1237

1338
/// An owned SQLite-representable value.
1439
Owned(Value),
40+
41+
/// A marker indicating to use the column's DEFAULT value.
42+
/// This is only valid for Appender operations.
43+
AppendDefault,
1544
}
1645

1746
// Generically allow any type that can be converted into a ValueRef
@@ -66,6 +95,7 @@ impl ToSql for ToSqlOutput<'_> {
6695
Ok(match *self {
6796
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
6897
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
98+
ToSqlOutput::AppendDefault => ToSqlOutput::AppendDefault,
6999
})
70100
}
71101
}
@@ -211,6 +241,13 @@ impl ToSql for std::time::Duration {
211241
}
212242
}
213243

244+
impl ToSql for AppendDefault {
245+
#[inline]
246+
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
247+
Ok(ToSqlOutput::AppendDefault)
248+
}
249+
}
250+
214251
#[cfg(test)]
215252
mod test {
216253
use super::ToSql;

0 commit comments

Comments
 (0)