Skip to content

Commit 638e4df

Browse files
committed
Document behavior of INSERT with RETURNING through tests
1 parent 3fe3a79 commit 638e4df

File tree

1 file changed

+71
-0
lines changed

1 file changed

+71
-0
lines changed

crates/duckdb/src/statement.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,77 @@ mod test {
820820
Ok(())
821821
}
822822

823+
// When using RETURNING clauses, DuckDB core treats the statement as a query result instead of a modification
824+
// statement. This causes execute() to return 0 changed rows and insert() to fail with an error.
825+
// This test demonstrates current behavior and proper usage patterns for RETURNING clauses.
826+
#[test]
827+
fn test_insert_with_returning_clause() -> Result<()> {
828+
let db = Connection::open_in_memory()?;
829+
db.execute_batch(
830+
"CREATE SEQUENCE location_id_seq START WITH 1 INCREMENT BY 1;
831+
CREATE TABLE location (
832+
id INTEGER PRIMARY KEY DEFAULT nextval('location_id_seq'),
833+
name TEXT NOT NULL
834+
)",
835+
)?;
836+
837+
// INSERT without RETURNING using execute
838+
let changes = db.execute("INSERT INTO location (name) VALUES (?)", ["test1"])?;
839+
assert_eq!(changes, 1);
840+
841+
// INSERT with RETURNING using execute - returns 0 (known limitation)
842+
let changes = db.execute("INSERT INTO location (name) VALUES (?) RETURNING id", ["test2"])?;
843+
assert_eq!(changes, 0);
844+
845+
// Verify the row was actually inserted despite returning 0
846+
let count: i64 = db.query_row("SELECT COUNT(*) FROM location", [], |r| r.get(0))?;
847+
assert_eq!(count, 2);
848+
849+
// INSERT without RETURNING using insert
850+
let mut stmt = db.prepare("INSERT INTO location (name) VALUES (?)")?;
851+
stmt.insert(["test3"])?;
852+
853+
// INSERT with RETURNING using insert - fails (known limitation)
854+
let mut stmt = db.prepare("INSERT INTO location (name) VALUES (?) RETURNING id")?;
855+
let result = stmt.insert(["test4"]);
856+
assert!(matches!(result, Err(Error::StatementChangedRows(0))));
857+
858+
// Verify the row was still inserted despite the error
859+
let count: i64 = db.query_row("SELECT COUNT(*) FROM location", [], |r| r.get(0))?;
860+
assert_eq!(count, 4);
861+
862+
// Proper way to use RETURNING - with query_row
863+
let id: i64 = db.query_row("INSERT INTO location (name) VALUES (?) RETURNING id", ["test5"], |r| {
864+
r.get(0)
865+
})?;
866+
assert_eq!(id, 5);
867+
868+
// Proper way to use RETURNING - with query_map
869+
let mut stmt = db.prepare("INSERT INTO location (name) VALUES (?) RETURNING id")?;
870+
let ids: Vec<i64> = stmt
871+
.query_map(["test6"], |row| row.get(0))?
872+
.collect::<Result<Vec<_>>>()?;
873+
assert_eq!(ids.len(), 1);
874+
assert_eq!(ids[0], 6);
875+
876+
// Proper way to use RETURNING - with query_one
877+
let id: i64 = db
878+
.prepare("INSERT INTO location (name) VALUES (?) RETURNING id")?
879+
.query_one(["test7"], |r| r.get(0))?;
880+
assert_eq!(id, 7);
881+
882+
// Multiple RETURNING columns
883+
let (id, name): (i64, String) = db.query_row(
884+
"INSERT INTO location (name) VALUES (?) RETURNING id, name",
885+
["test8"],
886+
|r| Ok((r.get(0)?, r.get(1)?)),
887+
)?;
888+
assert_eq!(id, 8);
889+
assert_eq!(name, "test8");
890+
891+
Ok(())
892+
}
893+
823894
#[test]
824895
fn test_exists() -> Result<()> {
825896
let db = Connection::open_in_memory()?;

0 commit comments

Comments
 (0)