ext/python: Basic support for placeholding insert

This commit is contained in:
Diego Reis 2025-03-20 17:05:39 -03:00
parent 2481d73d70
commit 16b9325830
3 changed files with 73 additions and 8 deletions

View file

@ -1,8 +1,11 @@
use anyhow::Result;
use errors::*;
use limbo_core::types::Text;
use limbo_core::OwnedValue;
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyList, PyTuple};
use std::cell::RefCell;
use std::num::NonZeroUsize;
use std::rc::Rc;
use std::sync::Arc;
@ -62,13 +65,6 @@ pub struct Cursor {
smt: Option<Rc<RefCell<limbo_core::Statement>>>,
}
#[pyclass(unsendable)]
#[derive(Clone)]
pub struct Connection {
conn: Rc<limbo_core::Connection>,
io: Arc<dyn limbo_core::IO>,
}
#[allow(unused_variables, clippy::arc_with_non_send_sync)]
#[pymethods]
impl Cursor {
@ -83,6 +79,20 @@ impl Cursor {
let stmt = Rc::new(RefCell::new(statement));
Python::with_gil(|py| {
if let Some(params) = parameters {
let obj = params.into_bound(py);
for (i, elem) in obj.iter().enumerate() {
let value = py_to_owned_value(&elem)?;
stmt.borrow_mut()
.bind_at(NonZeroUsize::new(i + 1).unwrap(), value);
}
}
Ok::<(), anyhow::Error>(())
})?;
// For DDL and DML statements,
// we need to execute the statement immediately
if stmt_is_ddl || stmt_is_dml {
@ -307,6 +317,27 @@ fn row_to_py(py: Python, row: &limbo_core::Row) -> Result<PyObject> {
.into())
}
/// Converts a Python object to a Limbo OwnedValue
fn py_to_owned_value(obj: &Bound<PyAny>) -> Result<limbo_core::OwnedValue> {
if obj.is_none() {
return Ok(OwnedValue::Null);
} else if let Ok(integer) = obj.extract::<i64>() {
return Ok(OwnedValue::Integer(integer));
} else if let Ok(float) = obj.extract::<f64>() {
return Ok(OwnedValue::Float(float));
} else if let Ok(string) = obj.extract::<String>() {
return Ok(OwnedValue::Text(Text::from_str(string)));
} else if let Ok(bytes) = obj.downcast::<PyBytes>() {
return Ok(OwnedValue::Blob(Rc::new(bytes.as_bytes().to_vec())));
} else {
return Err(PyErr::new::<ProgrammingError, _>(format!(
"Unsupported Python type: {}",
obj.get_type().name()?
))
.into());
}
}
#[pymodule]
fn _limbo(m: &Bound<PyModule>) -> PyResult<()> {
m.add("__version__", env!("CARGO_PKG_VERSION"))?;

View file

@ -66,6 +66,40 @@ def test_fetchone_select_max_user_id(provider):
assert max_id
assert max_id == (2,)
# Test case for: https://github.com/tursodatabase/limbo/issues/494
@pytest.mark.parametrize("provider", ["sqlite3", "limbo"])
def test_commit(provider):
con = limbo.connect("sqlite.db")
cur = con.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT NOT NULL,
role TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT (datetime('now'))
)
""")
con.commit()
sample_users = [
("alice", "alice@example.com", "admin"),
("bob", "bob@example.com", "user"),
("charlie", "charlie@example.com", "moderator"),
("diana", "diana@example.com", "user")
]
for username, email, role in sample_users:
cur.execute("INSERT INTO users (username, email, role) VALUES (?, ?, ?)", (username, email, role))
con.commit()
# Now query the table
res = cur.execute("SELECT * FROM users")
record = res.fetchone()
assert record
def connect(provider, database):
if provider == "limbo":

View file

@ -14,7 +14,7 @@ pub mod result;
mod schema;
mod storage;
mod translate;
mod types;
pub mod types;
#[allow(dead_code)]
mod util;
mod vdbe;