Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions crates/duckdb/examples/appender.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use duckdb::{params, Connection, DropBehavior, Result};
use duckdb::{params, types::AppendDefault, Connection, DropBehavior, Result};

fn main() -> Result<()> {
//let mut db = Connection::open("10m.db")?;
Expand All @@ -10,7 +10,7 @@ fn main() -> Result<()> {
id INTEGER not null, -- primary key,
area CHAR(6),
age TINYINT not null,
active TINYINT not null
active TINYINT DEFAULT 1,
);";
db.execute_batch(create_table_sql)?;

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

for i in 0..row_count {
app.append_row(params![
i,
get_random_area_code(),
get_random_age(),
get_random_active(),
])?;
app.append_row(params![i, get_random_area_code(), get_random_age(), AppendDefault])?;
}
}

Expand Down
61 changes: 59 additions & 2 deletions crates/duckdb/src/appender/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ impl Appender<'_> {
result_from_duckdb_appender(rc, &mut self.app)
}

/// Append a DEFAULT value to the current row
#[inline]
fn append_default(&mut self) -> Result<()> {
let rc = unsafe { ffi::duckdb_append_default(self.app) };
if rc != 0 {
return Err(Error::AppendError);
}
Ok(())
}

#[inline]
pub(crate) fn bind_parameters<P>(&mut self, params: P) -> Result<()>
where
Expand All @@ -95,13 +105,14 @@ impl Appender<'_> {
Ok(())
}

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

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

#[cfg(test)]
mod test {
use crate::{params, Connection, Error, Result};
use crate::{params, types::AppendDefault, Connection, Error, Result};

#[test]
fn test_append_one_row() -> Result<()> {
Expand Down Expand Up @@ -389,4 +400,50 @@ mod test {

Ok(())
}

#[test]
fn test_append_default() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch(
"CREATE TABLE test (
id INTEGER,
name VARCHAR,
status VARCHAR DEFAULT 'active'
)",
)?;

{
let mut app = db.appender("test")?;
app.append_row(params![1, "Alice", AppendDefault])?;
app.append_row(params![2, "Bob", AppendDefault])?;
app.append_row(params![3, AppendDefault, AppendDefault])?;
app.append_row(params![4, None::<String>, "inactive"])?;
}

let rows: Vec<(i32, Option<String>, String)> = db
.prepare("SELECT id, name, status FROM test ORDER BY id")?
.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
.collect::<Result<Vec<_>>>()?;

assert_eq!(rows.len(), 4);
assert_eq!(rows[0], (1, Some("Alice".to_string()), "active".to_string()));
assert_eq!(rows[1], (2, Some("Bob".to_string()), "active".to_string()));
assert_eq!(rows[2], (3, None, "active".to_string()));
assert_eq!(rows[3], (4, None, "inactive".to_string()));

Ok(())
}

#[test]
fn test_append_default_in_prepared_statement_fails() -> Result<()> {
let db = Connection::open_in_memory()?;
db.execute_batch("CREATE TABLE test (id INTEGER, name VARCHAR DEFAULT 'test')")?;

let mut stmt = db.prepare("INSERT INTO test VALUES (?, ?)")?;
let result = stmt.execute(params![1, AppendDefault]);

assert!(matches!(result, Err(Error::ToSqlConversionFailure(_))));

Ok(())
}
}
6 changes: 6 additions & 0 deletions crates/duckdb/src/pragma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ impl Sql {
let value = match value {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
ToSqlOutput::AppendDefault => {
return Err(Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"AppendDefault is only valid for Appender operations, not for pragmas",
))));
}
};
match value {
ValueRef::BigInt(i) => {
Expand Down
6 changes: 6 additions & 0 deletions crates/duckdb/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,12 @@ impl Statement<'_> {
let value = match value {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
ToSqlOutput::AppendDefault => {
return Err(Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"AppendDefault is only valid for Appender operations, not for prepared statements",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is a limitation of the C API as there's no duckdb_bind_default or similar.

))));
}
};
// TODO: bind more
let rc = match value {
Expand Down
1 change: 1 addition & 0 deletions crates/duckdb/src/types/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ mod test {
let value = match sqled {
ToSqlOutput::Borrowed(v) => v,
ToSqlOutput::Owned(ref v) => ValueRef::from(v),
ToSqlOutput::AppendDefault => unreachable!(),
};
let reversed = FromSql::column_result(value).unwrap();

Expand Down
2 changes: 1 addition & 1 deletion crates/duckdb/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use self::{
from_sql::{FromSql, FromSqlError, FromSqlResult},
ordered_map::OrderedMap,
string::DuckString,
to_sql::{ToSql, ToSqlOutput},
to_sql::{AppendDefault, ToSql, ToSqlOutput},
value::Value,
value_ref::{EnumType, ListType, TimeUnit, ValueRef},
};
Expand Down
43 changes: 43 additions & 0 deletions crates/duckdb/src/types/to_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@ use super::{Null, TimeUnit, Value, ValueRef};
use crate::Result;
use std::borrow::Cow;

/// Marker type that can be used in Appender params to indicate DEFAULT value.
///
/// This is useful when you want to append a row with some columns using their
/// default values (as defined in the table schema). Unlike `Null` which explicitly
/// sets a column to NULL, `AppendDefault` uses the column's DEFAULT expression.
///
/// ## Limitations
///
/// `AppendDefault` only works with **constant** default values. Non-deterministic
/// defaults like `random()` or `nextval()` are not supported. Explicitly provide
/// values for those columns as a workaround.
///
/// ## Example
///
/// ```rust,no_run
/// # use duckdb::{Connection, Result, params};
/// # use duckdb::types::AppendDefault;
///
/// fn append_with_default(conn: &Connection) -> Result<()> {
/// conn.execute_batch(
/// "CREATE TABLE people (id INTEGER, name VARCHAR, status VARCHAR DEFAULT 'active')"
/// )?;
///
/// let mut app = conn.appender("people")?;
/// app.append_row(params![1, "Alice", AppendDefault])?; // status will be 'active'
/// Ok(())
/// }
/// ```
#[derive(Copy, Clone, Debug)]
pub struct AppendDefault;

/// `ToSqlOutput` represents the possible output types for implementers of the
/// [`ToSql`] trait.
#[derive(Clone, Debug, PartialEq)]
Expand All @@ -12,6 +43,10 @@ pub enum ToSqlOutput<'a> {

/// An owned SQLite-representable value.
Owned(Value),

/// A marker indicating to use the column's DEFAULT value.
/// This is only valid for Appender operations.
AppendDefault,
}

// Generically allow any type that can be converted into a ValueRef
Expand Down Expand Up @@ -66,6 +101,7 @@ impl ToSql for ToSqlOutput<'_> {
Ok(match *self {
ToSqlOutput::Borrowed(v) => ToSqlOutput::Borrowed(v),
ToSqlOutput::Owned(ref v) => ToSqlOutput::Borrowed(ValueRef::from(v)),
ToSqlOutput::AppendDefault => ToSqlOutput::AppendDefault,
})
}
}
Expand Down Expand Up @@ -211,6 +247,13 @@ impl ToSql for std::time::Duration {
}
}

impl ToSql for AppendDefault {
#[inline]
fn to_sql(&self) -> Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::AppendDefault)
}
}

#[cfg(test)]
mod test {
use super::ToSql;
Expand Down