Merge 'bindings/rust: Add Statement.columns() support' from Timo Kösters

This PR adds the statement.columns() function, inspired from Rusqlite: h
ttps://docs.rs/rusqlite/latest/rusqlite/struct.Statement.html#method.col
umns
Note that the rusqlite documentation says
> If associated DB schema can be altered concurrently, you should make
sure that current statement has already been stepped once before calling
this method.
Do we have this requirement as well?
The first commit is just the rust binding. The second commit implements
the column name for the rowid column.

Closes #1376
This commit is contained in:
Pekka Enberg
2025-04-22 10:52:25 +03:00
5 changed files with 56 additions and 10 deletions

View File

@@ -138,7 +138,7 @@ pub extern "system" fn Java_tech_turso_core_LimboStatement_columns<'local>(
for i in 0..num_columns {
let column_name = stmt.stmt.get_column_name(i);
let str = env.new_string(column_name.as_str()).unwrap();
let str = env.new_string(column_name.into_owned()).unwrap();
env.set_object_array_element(&obj_arr, i as i32, str)
.unwrap();
}

View File

@@ -190,6 +190,39 @@ impl Statement {
}
}
}
pub fn columns(&self) -> Vec<Column> {
let stmt = self.inner.lock().unwrap();
let n = stmt.num_columns();
let mut cols = Vec::with_capacity(n);
for i in 0..n {
let name = stmt.get_column_name(i).into_owned();
cols.push(Column {
name,
decl_type: None, // TODO
});
}
cols
}
}
pub struct Column {
name: String,
decl_type: Option<String>,
}
impl Column {
pub fn name(&self) -> &str {
&self.name
}
pub fn decl_type(&self) -> Option<&str> {
self.decl_type.as_deref()
}
}
pub trait IntoValue {

View File

@@ -591,7 +591,7 @@ impl Statement {
self.program.result_columns.len()
}
pub fn get_column_name(&self, idx: usize) -> Cow<String> {
pub fn get_column_name(&self, idx: usize) -> Cow<str> {
let column = &self.program.result_columns[idx];
match column.name(&self.program.table_references) {
Some(name) => Cow::Borrowed(name),

View File

@@ -34,13 +34,26 @@ pub struct ResultSetColumn {
}
impl ResultSetColumn {
pub fn name<'a>(&'a self, tables: &'a [TableReference]) -> Option<&'a String> {
pub fn name<'a>(&'a self, tables: &'a [TableReference]) -> Option<&'a str> {
if let Some(alias) = &self.alias {
return Some(alias);
}
match &self.expr {
ast::Expr::Column { table, column, .. } => {
tables[*table].columns()[*column].name.as_ref()
tables[*table].columns()[*column].name.as_deref()
}
ast::Expr::RowId { table, .. } => {
// If there is a rowid alias column, use its name
if let Table::BTree(table) = &tables[*table].table {
if let Some(rowid_alias_column) = table.get_rowid_alias_column() {
if let Some(name) = &rowid_alias_column.1.name {
return Some(name);
}
}
}
// If there is no rowid alias, use "rowid".
Some("rowid")
}
_ => None,
}
@@ -465,7 +478,7 @@ impl TableReference {
plan.result_columns
.iter()
.map(|rc| Column {
name: rc.name(&plan.table_references).map(String::clone),
name: rc.name(&plan.table_references).map(String::from),
ty: Type::Text, // FIXME: infer proper type
ty_str: "TEXT".to_string(),
is_rowid_alias: false,

View File

@@ -120,16 +120,16 @@ mod tests {
let columns = stmt.num_columns();
assert_eq!(columns, 3);
assert_eq!(stmt.get_column_name(0), "foo".into());
assert_eq!(stmt.get_column_name(1), "bar".into());
assert_eq!(stmt.get_column_name(2), "baz".into());
assert_eq!(stmt.get_column_name(0), "foo");
assert_eq!(stmt.get_column_name(1), "bar");
assert_eq!(stmt.get_column_name(2), "baz");
let stmt = conn.prepare("select foo, bar from test;")?;
let columns = stmt.num_columns();
assert_eq!(columns, 2);
assert_eq!(stmt.get_column_name(0), "foo".into());
assert_eq!(stmt.get_column_name(1), "bar".into());
assert_eq!(stmt.get_column_name(0), "foo");
assert_eq!(stmt.get_column_name(1), "bar");
let stmt = conn.prepare("delete from test;")?;
let columns = stmt.num_columns();