Skip to content

Commit b1966bb

Browse files
committed
Merge remote-tracking branch 'origin/main' into scalar-with-state
2 parents 1a94f22 + 5073edf commit b1966bb

File tree

9 files changed

+214
-50
lines changed

9 files changed

+214
-50
lines changed

README.md

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -127,31 +127,57 @@ declarations for DuckDB's C API. By default, `libduckdb-sys` attempts to find a
127127

128128
You can adjust this behavior in a number of ways:
129129

130-
- If you use the `bundled` feature, `libduckdb-sys` will use the
131-
[cc](https://crates.io/crates/cc) crate to compile DuckDB from source and
132-
link against that. This source is embedded in the `libduckdb-sys` crate and
133-
as we are still in development, we will update it regularly. After we are more stable,
134-
we will use the stable released version from [duckdb](https://github.com/duckdb/duckdb/releases).
135-
This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
136-
137-
```bash
138-
cargo add duckdb --features bundled
139-
```
140-
141-
`Cargo.toml` will be updated.
142-
143-
```toml
144-
[dependencies]
145-
# Assume that version DuckDB version 0.9.2 is used.
146-
duckdb = { version = "0.9.2", features = ["bundled"] }
147-
```
148-
149-
* When linking against a DuckDB library already on the system (so _not_ using any of the `bundled` features), you can set the `DUCKDB_LIB_DIR` environment variable to point to a directory containing the library. You can also set the `DUCKDB_INCLUDE_DIR` variable to point to the directory containing `duckdb.h`.
150-
* Installing the duckdb development packages will usually be all that is required, but
151-
the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
152-
and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
153-
options. The default when using vcpkg is to dynamically link,
154-
which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build.
130+
1. If you use the `bundled` feature, `libduckdb-sys` will use the
131+
[cc](https://crates.io/crates/cc) crate to compile DuckDB from source and
132+
link against that. This source is embedded in the `libduckdb-sys` crate and
133+
as we are still in development, we will update it regularly. After we are more stable,
134+
we will use the stable released version from [duckdb](https://github.com/duckdb/duckdb/releases).
135+
This is probably the simplest solution to any build problems. You can enable this by adding the following in your `Cargo.toml` file:
136+
137+
```bash
138+
cargo add duckdb --features bundled
139+
```
140+
141+
`Cargo.toml` will be updated.
142+
143+
```toml
144+
[dependencies]
145+
duckdb = { version = "1.3.2", features = ["bundled"] }
146+
```
147+
148+
2. When linking against a DuckDB library already on the system (so _not_ using any of the `bundled` features), you can set the `DUCKDB_LIB_DIR` environment variable to point to a directory containing the library. You can also set the `DUCKDB_INCLUDE_DIR` variable to point to the directory containing `duckdb.h`.
149+
150+
Linux example:
151+
152+
```shell
153+
wget https://github.com/duckdb/duckdb/releases/download/v1.3.2/libduckdb-linux-arm64.zip
154+
unzip libduckdb-linux-arm64.zip -d libduckdb
155+
156+
export DUCKDB_LIB_DIR=$PWD/libduckdb
157+
export DUCKDB_INCLUDE_DIR=$DUCKDB_LIB_DIR
158+
export LD_LIBRARY_PATH=$DUCKDB_LIB_DIR
159+
160+
cargo build --examples
161+
```
162+
163+
macOS example:
164+
165+
```shell
166+
wget https://github.com/duckdb/duckdb/releases/download/v1.3.2/libduckdb-osx-universal.zip
167+
unzip libduckdb-osx-universal.zip -d libduckdb
168+
169+
export DUCKDB_LIB_DIR=$PWD/libduckdb
170+
export DUCKDB_INCLUDE_DIR=$DUCKDB_LIB_DIR
171+
export DYLD_FALLBACK_LIBRARY_PATH=$DUCKDB_LIB_DIR
172+
173+
cargo build --examples
174+
```
175+
176+
3. Installing the duckdb development packages will usually be all that is required, but
177+
the build helpers for [pkg-config](https://github.com/alexcrichton/pkg-config-rs)
178+
and [vcpkg](https://github.com/mcgoo/vcpkg-rs) have some additional configuration
179+
options. The default when using vcpkg is to dynamically link,
180+
which must be enabled by setting `VCPKGRS_DYNAMIC=1` environment variable before build.
155181

156182
### Binding generation
157183

crates/duckdb/src/appender/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,7 @@ impl Appender<'_> {
161161
impl Drop for Appender<'_> {
162162
fn drop(&mut self) {
163163
if !self.app.is_null() {
164-
let _ = self.flush(); // can't safely handle failures here
165164
unsafe {
166-
ffi::duckdb_appender_close(self.app);
167165
ffi::duckdb_appender_destroy(&mut self.app);
168166
}
169167
}

crates/duckdb/src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ pub enum Error {
4747
/// for [`query_row`](crate::Connection::query_row)) did not return any.
4848
QueryReturnedNoRows,
4949

50+
/// Error when a query that was expected to return only one row (e.g.,
51+
/// for [`query_one`](crate::Connection::query_one)) did return more than one.
52+
QueryReturnedMoreThanOneRow,
53+
5054
/// Error when the value of a particular column is requested, but the index
5155
/// is out of range for the statement.
5256
InvalidColumnIndex(usize),
@@ -96,6 +100,7 @@ impl PartialEq for Error {
96100
(Self::InvalidPath(p1), Self::InvalidPath(p2)) => p1 == p2,
97101
(Self::ExecuteReturnedResults, Self::ExecuteReturnedResults) => true,
98102
(Self::QueryReturnedNoRows, Self::QueryReturnedNoRows) => true,
103+
(Self::QueryReturnedMoreThanOneRow, Self::QueryReturnedMoreThanOneRow) => true,
99104
(Self::InvalidColumnIndex(i1), Self::InvalidColumnIndex(i2)) => i1 == i2,
100105
(Self::InvalidColumnName(n1), Self::InvalidColumnName(n2)) => n1 == n2,
101106
(Self::InvalidColumnType(i1, n1, t1), Self::InvalidColumnType(i2, n2, t2)) => {
@@ -170,6 +175,7 @@ impl fmt::Display for Error {
170175
write!(f, "Execute returned results - did you mean to call query?")
171176
}
172177
Self::QueryReturnedNoRows => write!(f, "Query returned no rows"),
178+
Self::QueryReturnedMoreThanOneRow => write!(f, "Query returned more than one row"),
173179
Self::InvalidColumnIndex(i) => write!(f, "Invalid column index: {i}"),
174180
Self::InvalidColumnName(ref name) => write!(f, "Invalid column name: {name}"),
175181
Self::InvalidColumnType(i, ref name, ref t) => {
@@ -201,6 +207,7 @@ impl error::Error for Error {
201207
| Self::InvalidParameterName(_)
202208
| Self::ExecuteReturnedResults
203209
| Self::QueryReturnedNoRows
210+
| Self::QueryReturnedMoreThanOneRow
204211
| Self::InvalidColumnIndex(_)
205212
| Self::InvalidColumnName(_)
206213
| Self::InvalidColumnType(..)

crates/duckdb/src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,32 @@ mod test {
12381238
}
12391239
Ok(())
12401240
}
1241+
1242+
#[test]
1243+
fn test_rows_and_then_with_custom_error() -> Result<()> {
1244+
let db = checked_memory_handle();
1245+
db.execute_batch("CREATE TABLE test (value INTEGER)")?;
1246+
db.execute_batch("INSERT INTO test VALUES (1), (3), (5)")?;
1247+
1248+
let mut stmt = db.prepare("SELECT value FROM test ORDER BY value")?;
1249+
let rows = stmt.query([])?;
1250+
1251+
// Use and_then to apply custom validation with custom error type
1252+
let results: Vec<i32> = rows
1253+
.and_then(|row| -> CustomResult<i32> {
1254+
let val: i32 = row.get(0)?; // duckdb::Error automatically converted via From trait
1255+
if val > 10 {
1256+
Err(CustomError::SomeError) // Custom application-specific error
1257+
} else {
1258+
Ok(val)
1259+
}
1260+
})
1261+
.collect::<CustomResult<Vec<_>>>()
1262+
.unwrap();
1263+
1264+
assert_eq!(results, vec![1, 3, 5]);
1265+
Ok(())
1266+
}
12411267
}
12421268

12431269
#[test]

crates/duckdb/src/row.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ impl<'stmt> Rows<'stmt> {
5858

5959
/// Map over this `Rows`, converting it to a [`Map`], which
6060
/// implements `FallibleIterator`.
61+
///
62+
/// **Note:** This method requires the closure to return `duckdb::Result<B>`.
63+
/// If you need to use custom error types, consider using [`and_then`](Self::and_then)
64+
/// instead, which allows any error type that implements `From<duckdb::Error>`.
65+
///
6166
/// ```rust,no_run
6267
/// use fallible_iterator::FallibleIterator;
6368
/// # use duckdb::{Result, Statement};

crates/duckdb/src/statement.rs

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,33 @@ impl Statement<'_> {
349349
self.query(params)?.get_expected_row().and_then(f)
350350
}
351351

352+
/// Convenience method to execute a query that is expected to return exactly
353+
/// one row.
354+
///
355+
/// Returns `Err(QueryReturnedMoreThanOneRow)` if the query returns more than one row.
356+
///
357+
/// Returns `Err(QueryReturnedNoRows)` if no results are returned. If the
358+
/// query truly is optional, you can call
359+
/// [`.optional()`](crate::OptionalExt::optional) on the result of
360+
/// this to get a `Result<Option<T>>` (requires that the trait
361+
/// `duckdb::OptionalExt` is imported).
362+
///
363+
/// # Failure
364+
///
365+
/// Will return `Err` if the underlying DuckDB call fails.
366+
pub fn query_one<T, P, F>(&mut self, params: P, f: F) -> Result<T>
367+
where
368+
P: Params,
369+
F: FnOnce(&Row<'_>) -> Result<T>,
370+
{
371+
let mut rows = self.query(params)?;
372+
let row = rows.get_expected_row().and_then(f)?;
373+
if rows.next()?.is_some() {
374+
return Err(Error::QueryReturnedMoreThanOneRow);
375+
}
376+
Ok(row)
377+
}
378+
352379
/// Return the row count
353380
#[inline]
354381
pub fn row_count(&self) -> usize {
@@ -635,9 +662,8 @@ mod test {
635662

636663
let mut stmt = db.prepare("SELECT id FROM test where name = ?")?;
637664
{
638-
let mut rows = stmt.query([&"one"])?;
639-
let id: Result<i32> = rows.next()?.unwrap().get(0);
640-
assert_eq!(Ok(1), id);
665+
let id: i32 = stmt.query_one([&"one"], |r| r.get(0))?;
666+
assert_eq!(id, 1);
641667
}
642668
Ok(())
643669
}
@@ -825,6 +851,71 @@ mod test {
825851
Ok(())
826852
}
827853

854+
#[test]
855+
fn test_query_one() -> Result<()> {
856+
let db = Connection::open_in_memory()?;
857+
let sql = "BEGIN;
858+
CREATE TABLE foo(x INTEGER, y INTEGER);
859+
INSERT INTO foo VALUES(1, 3);
860+
INSERT INTO foo VALUES(2, 4);
861+
END;";
862+
db.execute_batch(sql)?;
863+
864+
// Exactly one row
865+
let y: i32 = db
866+
.prepare("SELECT y FROM foo WHERE x = ?")?
867+
.query_one([1], |r| r.get(0))?;
868+
assert_eq!(y, 3);
869+
870+
// No rows
871+
let res: Result<i32> = db
872+
.prepare("SELECT y FROM foo WHERE x = ?")?
873+
.query_one([99], |r| r.get(0));
874+
assert_eq!(res.unwrap_err(), Error::QueryReturnedNoRows);
875+
876+
// Multiple rows
877+
let res: Result<i32> = db.prepare("SELECT y FROM foo")?.query_one([], |r| r.get(0));
878+
assert_eq!(res.unwrap_err(), Error::QueryReturnedMoreThanOneRow);
879+
880+
Ok(())
881+
}
882+
883+
#[test]
884+
fn test_query_one_optional() -> Result<()> {
885+
use crate::OptionalExt;
886+
887+
let db = Connection::open_in_memory()?;
888+
let sql = "BEGIN;
889+
CREATE TABLE foo(x INTEGER, y INTEGER);
890+
INSERT INTO foo VALUES(1, 3);
891+
INSERT INTO foo VALUES(2, 4);
892+
END;";
893+
db.execute_batch(sql)?;
894+
895+
// Exactly one row
896+
let y: Option<i32> = db
897+
.prepare("SELECT y FROM foo WHERE x = ?")?
898+
.query_one([1], |r| r.get(0))
899+
.optional()?;
900+
assert_eq!(y, Some(3));
901+
902+
// No rows
903+
let y: Option<i32> = db
904+
.prepare("SELECT y FROM foo WHERE x = ?")?
905+
.query_one([99], |r| r.get(0))
906+
.optional()?;
907+
assert_eq!(y, None);
908+
909+
// Multiple rows - should still return error (not converted by optional)
910+
let res = db
911+
.prepare("SELECT y FROM foo")?
912+
.query_one([], |r| r.get::<_, i32>(0))
913+
.optional();
914+
assert_eq!(res.unwrap_err(), Error::QueryReturnedMoreThanOneRow);
915+
916+
Ok(())
917+
}
918+
828919
#[test]
829920
fn test_query_by_column_name() -> Result<()> {
830921
let db = Connection::open_in_memory()?;

crates/duckdb/src/types/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,6 @@ mod test {
312312

313313
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
314314
let mut rows = stmt.query([])?;
315-
316315
let row = rows.next()?.unwrap();
317316

318317
// check the correct types come back as expected
@@ -371,7 +370,6 @@ mod test {
371370

372371
let mut stmt = db.prepare("SELECT b, t, i, f, n FROM foo")?;
373372
let mut rows = stmt.query([])?;
374-
375373
let row = rows.next()?.unwrap();
376374
// NOTE: this is different from SQLite
377375
// assert_eq!(Value::Blob(vec![1, 2]), row.get::<_, Value>(0)?);

crates/duckdb/src/types/to_sql.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,7 @@ mod test {
316316

317317
db.execute("INSERT INTO foo (id) VALUES (gen_random_uuid())", [])?;
318318

319-
let mut stmt = db.prepare("SELECT id FROM foo")?;
320-
let mut rows = stmt.query([])?;
321-
let row = rows.next()?.unwrap();
322-
let found_id: String = row.get_unwrap(0);
319+
let found_id: String = db.prepare("SELECT id FROM foo")?.query_one([], |r| r.get(0))?;
323320
assert_eq!(found_id.len(), 36);
324321
Ok(())
325322
}
@@ -337,11 +334,9 @@ mod test {
337334
let id_vec = id.as_bytes().to_vec();
338335
db.execute("INSERT INTO foo (id, label) VALUES (?, ?)", params![id_vec, "target"])?;
339336

340-
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
341-
let mut rows = stmt.query(params![id_vec])?;
342-
let row = rows.next()?.unwrap();
343-
let found_id: Uuid = row.get_unwrap(0);
344-
let found_label: String = row.get_unwrap(1);
337+
let (found_id, found_label): (Uuid, String) = db
338+
.prepare("SELECT id, label FROM foo WHERE id = ?")?
339+
.query_one(params![id_vec], |r| Ok((r.get_unwrap(0), r.get_unwrap(1))))?;
345340
assert_eq!(found_id, id);
346341
assert_eq!(found_label, "target");
347342
Ok(())
@@ -359,11 +354,9 @@ mod test {
359354
let id = Uuid::new_v4();
360355
db.execute("INSERT INTO foo (id, label) VALUES (?, ?)", params![id, "target"])?;
361356

362-
let mut stmt = db.prepare("SELECT id, label FROM foo WHERE id = ?")?;
363-
let mut rows = stmt.query(params![id])?;
364-
let row = rows.next()?.unwrap();
365-
let found_id: Uuid = row.get_unwrap(0);
366-
let found_label: String = row.get_unwrap(1);
357+
let (found_id, found_label): (Uuid, String) = db
358+
.prepare("SELECT id, label FROM foo WHERE id = ?")?
359+
.query_one(params![id], |r| Ok((r.get_unwrap(0), r.get_unwrap(1))))?;
367360
assert_eq!(found_id, id);
368361
assert_eq!(found_label, "target");
369362
Ok(())

crates/duckdb/src/vtab/value.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::ffi::{duckdb_destroy_value, duckdb_get_int64, duckdb_get_varchar, duckdb_value};
2-
use std::{ffi::CString, fmt};
1+
use crate::ffi::{duckdb_destroy_value, duckdb_free, duckdb_get_int64, duckdb_get_varchar, duckdb_value};
2+
use std::{ffi::CStr, fmt, os::raw::c_void};
33

44
/// The Value object holds a single arbitrary value of any type that can be
55
/// stored in the database.
@@ -34,7 +34,27 @@ impl Value {
3434

3535
impl fmt::Display for Value {
3636
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37-
let c_string = unsafe { CString::from_raw(duckdb_get_varchar(self.ptr)) };
38-
write!(f, "{}", c_string.to_str().unwrap())
37+
unsafe {
38+
let varchar = duckdb_get_varchar(self.ptr);
39+
let c_str = CStr::from_ptr(varchar);
40+
let res = write!(f, "{}", c_str.to_string_lossy());
41+
duckdb_free(varchar as *mut c_void);
42+
res
43+
}
44+
}
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use super::*;
50+
use crate::ffi::duckdb_create_varchar;
51+
use std::ffi::CString;
52+
53+
#[test]
54+
fn test_value_to_string() {
55+
let c_str = CString::new("some value").unwrap();
56+
let duckdb_val = unsafe { duckdb_create_varchar(c_str.as_ptr()) };
57+
let val = Value::from(duckdb_val);
58+
assert_eq!(val.to_string(), "some value");
3959
}
4060
}

0 commit comments

Comments
 (0)