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(); .unwrap();
conn.pragma_query("journal_mode", |row| { conn.pragma_query("journal_mode", |row| {
println!("{}", row.get_value(0)); println!("{:?}", row.get_value(0));
Ok(()) Ok(())
}) })
.unwrap(); .unwrap();

View File

@@ -124,17 +124,28 @@ impl Connection {
Ok(statement) 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 where
F: FnMut(&limbo_core::Row) -> limbo_core::Result<()>, F: FnMut(&Row) -> limbo_core::Result<()>,
{ {
let conn = self let conn = self
.inner .inner
.lock() .lock()
.map_err(|e| Error::MutexError(e.to_string()))?; .map_err(|e| Error::MutexError(e.to_string()))?;
conn.pragma_query(pragma_name, f) let rows: Vec<Row> = conn
.map_err(|e| Error::SqlExecutionFailure(e.to_string())) .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() 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, SchemaLocked,
#[error("Database Connection is read-only")] #[error("Database Connection is read-only")]
ReadOnly, ReadOnly,
#[error("Database is busy")]
Busy,
} }
#[macro_export] #[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>; 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 { pub struct Database {
mv_store: Option<Rc<MvStore>>, mv_store: Option<Rc<MvStore>>,
schema: Arc<RwLock<Schema>>, schema: Arc<RwLock<Schema>>,
@@ -586,22 +582,30 @@ impl Connection {
Ok(()) Ok(())
} }
pub fn pragma_query<F>(self: &Rc<Connection>, pragma_name: &str, mut f: F) -> Result<()> // Clearly there is something to improve here, Vec<Vec<Value>> isn't a couple of tea
where pub fn pragma_query(self: &Rc<Connection>, pragma_name: &str) -> Result<Vec<Vec<Value>>> {
F: FnMut(&Row) -> Result<()>,
{
let pragma = format!("PRAGMA {}", pragma_name); let pragma = format!("PRAGMA {}", pragma_name);
let mut stmt = self.prepare(pragma)?; let mut stmt = self.prepare(pragma)?;
let mut results = Vec::new();
loop { loop {
match stmt.step()? { match stmt.step()? {
vdbe::StepResult::Row => { vdbe::StepResult::Row => {
let row = stmt.row().unwrap(); let row: Vec<Value> = stmt
f(row)?; .row()
.unwrap()
.get_values()
.map(|v| v.clone())
.collect();
results.push(row);
}
vdbe::StepResult::Interrupt | vdbe::StepResult::Busy => {
return Err(LimboError::Busy);
} }
_ => break, _ => break,
} }
} }
Ok(())
Ok(results)
} }
} }
@@ -681,6 +685,8 @@ impl Statement {
} }
} }
pub type Row = vdbe::Row;
pub type StepResult = vdbe::StepResult; pub type StepResult = vdbe::StepResult;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]