Skip to content

Commit 2e33f57

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 7976f1a commit 2e33f57

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
@@ -118,6 +118,16 @@ impl Appender<'_> {
118118
result_from_duckdb_appender(rc, &mut self.app)
119119
}
120120

121+
/// Append a DEFAULT value to the current row
122+
#[inline]
123+
fn append_default(&mut self) -> Result<()> {
124+
let rc = unsafe { ffi::duckdb_append_default(self.app) };
125+
if rc != 0 {
126+
return Err(Error::AppendError);
127+
}
128+
Ok(())
129+
}
130+
121131
#[inline]
122132
pub(crate) fn bind_parameters<P>(&mut self, params: P) -> Result<()>
123133
where
@@ -130,13 +140,14 @@ impl Appender<'_> {
130140
Ok(())
131141
}
132142

133-
fn bind_parameter<P: ?Sized + ToSql>(&self, param: &P) -> Result<()> {
143+
fn bind_parameter<P: ?Sized + ToSql>(&mut self, param: &P) -> Result<()> {
134144
let value = param.to_sql()?;
135145

136146
let ptr = self.app;
137147
let value = match value {
138148
ToSqlOutput::Borrowed(v) => v,
139149
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
150+
ToSqlOutput::AppendDefault => return self.append_default(),
140151
};
141152
// NOTE: we ignore the return value here
142153
// because if anything failed, end_row will fail
@@ -224,7 +235,7 @@ impl fmt::Debug for Appender<'_> {
224235

225236
#[cfg(test)]
226237
mod test {
227-
use crate::{params, Connection, Error, Result};
238+
use crate::{params, types::AppendDefault, Connection, Error, Result};
228239

229240
#[test]
230241
fn test_append_one_row() -> Result<()> {
@@ -424,4 +435,50 @@ mod test {
424435

425436
Ok(())
426437
}
438+
439+
#[test]
440+
fn test_append_default() -> Result<()> {
441+
let db = Connection::open_in_memory()?;
442+
db.execute_batch(
443+
"CREATE TABLE test (
444+
id INTEGER,
445+
name VARCHAR,
446+
status VARCHAR DEFAULT 'active'
447+
)",
448+
)?;
449+
450+
{
451+
let mut app = db.appender("test")?;
452+
app.append_row(params![1, "Alice", AppendDefault])?;
453+
app.append_row(params![2, "Bob", AppendDefault])?;
454+
app.append_row(params![3, AppendDefault, AppendDefault])?;
455+
app.append_row(params![4, None::<String>, "inactive"])?;
456+
}
457+
458+
let rows: Vec<(i32, Option<String>, String)> = db
459+
.prepare("SELECT id, name, status FROM test ORDER BY id")?
460+
.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
461+
.collect::<Result<Vec<_>>>()?;
462+
463+
assert_eq!(rows.len(), 4);
464+
assert_eq!(rows[0], (1, Some("Alice".to_string()), "active".to_string()));
465+
assert_eq!(rows[1], (2, Some("Bob".to_string()), "active".to_string()));
466+
assert_eq!(rows[2], (3, None, "active".to_string()));
467+
assert_eq!(rows[3], (4, None, "inactive".to_string()));
468+
469+
Ok(())
470+
}
471+
472+
#[test]
473+
fn test_append_default_in_prepared_statement_fails() -> Result<()> {
474+
let db = Connection::open_in_memory()?;
475+
db.execute_batch("CREATE TABLE test (id INTEGER, name VARCHAR DEFAULT 'test')")?;
476+
477+
let mut stmt = db.prepare("INSERT INTO test VALUES (?, ?)")?;
478+
let result = stmt.execute(params![1, AppendDefault]);
479+
480+
assert!(matches!(result, Err(Error::ToSqlConversionFailure(_))));
481+
482+
Ok(())
483+
}
427484
}

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
@@ -567,6 +567,12 @@ impl Statement<'_> {
567567
let value = match value {
568568
ToSqlOutput::Borrowed(v) => v,
569569
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
570+
ToSqlOutput::AppendDefault => {
571+
return Err(Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
572+
std::io::ErrorKind::InvalidInput,
573+
"AppendDefault is only valid for Appender operations, not for prepared statements",
574+
))));
575+
}
570576
};
571577
// TODO: bind more
572578
let rc = match value {

crates/duckdb/src/types/chrono.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ mod test {
319319
let value = match sqled {
320320
ToSqlOutput::Borrowed(v) => v,
321321
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
322+
ToSqlOutput::AppendDefault => unreachable!(),
322323
};
323324
let reversed = FromSql::column_result(value).unwrap();
324325

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)