bind/rust: Fix lifetime issue with pragma_query

Shallow cloning in Row ended up invalidating the pointer
to value
This commit is contained in:
Diego Reis 2025-05-19 20:46:22 -03:00
parent ed0e3b1ba2
commit 4766c9c286
4 changed files with 52 additions and 16 deletions

View file

@ -13,7 +13,7 @@ async fn main() {
.unwrap();
conn.pragma_query("journal_mode", |row| {
println!("{}", row.get_value(0));
println!("{:?}", row.get_value(0));
Ok(())
})
.unwrap();

View file

@ -124,17 +124,28 @@ impl Connection {
Ok(statement)
}
pub fn pragma_query<F>(&self, pragma_name: &str, f: F) -> Result<()>
pub fn pragma_query<F>(&self, pragma_name: &str, mut f: F) -> Result<()>
where
F: FnMut(&limbo_core::Row) -> limbo_core::Result<()>,
F: FnMut(&Row) -> limbo_core::Result<()>,
{
let conn = self
.inner
.lock()
.map_err(|e| Error::MutexError(e.to_string()))?;
conn.pragma_query(pragma_name, f)
.map_err(|e| Error::SqlExecutionFailure(e.to_string()))
let rows: Vec<Row> = conn
.pragma_query(pragma_name)
.map_err(|e| Error::SqlExecutionFailure(e.to_string()))?
.iter()
.map(|row| row.iter().collect::<Row>())
.collect();
rows.iter().try_for_each(|row| {
f(row).map_err(|e| {
Error::SqlExecutionFailure(format!("Error executing user defined function: {}", e))
})
})?;
Ok(())
}
}
@ -320,3 +331,20 @@ impl Row {
self.values.len()
}
}
impl<'a> FromIterator<&'a limbo_core::Value> for Row {
fn from_iter<T: IntoIterator<Item = &'a limbo_core::Value>>(iter: T) -> Self {
let values = iter
.into_iter()
.map(|v| match v {
limbo_core::Value::Integer(i) => limbo_core::Value::Integer(*i),
limbo_core::Value::Null => limbo_core::Value::Null,
limbo_core::Value::Float(f) => limbo_core::Value::Float(*f),
limbo_core::Value::Text(s) => limbo_core::Value::Text(s.clone()),
limbo_core::Value::Blob(b) => limbo_core::Value::Blob(b.clone()),
})
.collect();
Row { values }
}
}

View file

@ -53,6 +53,8 @@ pub enum LimboError {
SchemaLocked,
#[error("Database Connection is read-only")]
ReadOnly,
#[error("Database is busy")]
Busy,
}
#[macro_export]

View file

@ -89,10 +89,6 @@ pub(crate) type MvStore = mvcc::MvStore<mvcc::LocalClock>;
pub(crate) type MvCursor = mvcc::cursor::ScanCursor<mvcc::LocalClock>;
// Not so sure if we should expose this row to the user
// or make a wrapper around it to sharp some corners
pub type Row = vdbe::Row;
pub struct Database {
mv_store: Option<Rc<MvStore>>,
schema: Arc<RwLock<Schema>>,
@ -586,22 +582,30 @@ impl Connection {
Ok(())
}
pub fn pragma_query<F>(self: &Rc<Connection>, pragma_name: &str, mut f: F) -> Result<()>
where
F: FnMut(&Row) -> Result<()>,
{
// Clearly there is something to improve here, Vec<Vec<Value>> isn't a couple of tea
pub fn pragma_query(self: &Rc<Connection>, pragma_name: &str) -> Result<Vec<Vec<Value>>> {
let pragma = format!("PRAGMA {}", pragma_name);
let mut stmt = self.prepare(pragma)?;
let mut results = Vec::new();
loop {
match stmt.step()? {
vdbe::StepResult::Row => {
let row = stmt.row().unwrap();
f(row)?;
let row: Vec<Value> = stmt
.row()
.unwrap()
.get_values()
.map(|v| v.clone())
.collect();
results.push(row);
}
vdbe::StepResult::Interrupt | vdbe::StepResult::Busy => {
return Err(LimboError::Busy);
}
_ => break,
}
}
Ok(())
Ok(results)
}
}
@ -681,6 +685,8 @@ impl Statement {
}
}
pub type Row = vdbe::Row;
pub type StepResult = vdbe::StepResult;
#[derive(Clone, Debug)]