Repository: tursodatabase/limbo (language: rust)

Issue:
Add default column name
The current `get_column_name(i)` return an `Option<&String>`, which works well when an `alias` is set, but it returns `None` for expr.

###### Limbo
```sql
SELECT 123 as number;   -- Some("number")
SELECT 123;             -- None
SELECT 12 + 3;          -- None
```

###### SQLite / DuckDB
```sql
SELECT 123 as number;   -- "number"
SELECT 123;             -- "123"
SELECT 12 + 3;          -- "12 + 3"
```

We should use the expr itself as the column name?  `self.expr.to_string()`
https://github.com/tursodatabase/limbo/blob/9657920dd7ad77c4f9dfc213381d394395d526fb/core/translate/plan.rs#L27-L39


Discussion / hints:
Yes, good idea!

Fix patch:
diff --git a/bindings/go/rs_src/rows.rs b/bindings/go/rs_src/rows.rs
index ae2094d60..f296c9076 100644
--- a/bindings/go/rs_src/rows.rs
+++ b/bindings/go/rs_src/rows.rs
@@ -115,12 +115,7 @@ pub extern "C" fn rows_get_column_name(rows_ptr: *mut c_void, idx: i32) -> *cons
         return std::ptr::null_mut();
     }
     let name = rows.stmt.get_column_name(idx as usize);
-    let cstr = std::ffi::CString::new(
-        name.as_ref()
-            .unwrap_or(&&format!("column_{}", idx))
-            .as_bytes(),
-    )
-    .expect("Failed to create CString");
+    let cstr = std::ffi::CString::new(name.as_bytes()).expect("Failed to create CString");
     cstr.into_raw() as *const c_char
 }
 
diff --git a/bindings/java/rs_src/limbo_statement.rs b/bindings/java/rs_src/limbo_statement.rs
index efd87681d..b0cd44a04 100644
--- a/bindings/java/rs_src/limbo_statement.rs
+++ b/bindings/java/rs_src/limbo_statement.rs
@@ -138,11 +138,10 @@ pub extern "system" fn Java_org_github_tursodatabase_core_LimboStatement_columns
         .unwrap();
 
     for i in 0..num_columns {
-        if let Some(column_name) = stmt.stmt.get_column_name(i) {
-            let str = env.new_string(column_name).unwrap();
-            env.set_object_array_element(&obj_arr, i as i32, str)
-                .unwrap();
-        }
+        let column_name = stmt.stmt.get_column_name(i);
+        let str = env.new_string(column_name.as_str()).unwrap();
+        env.set_object_array_element(&obj_arr, i as i32, str)
+            .unwrap();
     }
 
     obj_arr.into()
diff --git a/cli/app.rs b/cli/app.rs
index b44bb0437..7941e90e1 100644
--- a/cli/app.rs
+++ b/cli/app.rs
@@ -686,7 +686,7 @@ impl Limbo {
                     if rows.num_columns() > 0 {
                         let header = (0..rows.num_columns())
                             .map(|i| {
-                                let name = rows.get_column_name(i).cloned().unwrap_or_default();
+                                let name = rows.get_column_name(i);
                                 Cell::new(name).add_attribute(Attribute::Bold)
                             })
                             .collect::<Vec<_>>();
diff --git a/core/lib.rs b/core/lib.rs
index 86f7b4700..dee3f8919 100644
--- a/core/lib.rs
+++ b/core/lib.rs
@@ -32,6 +32,7 @@ use log::trace;
 use parking_lot::RwLock;
 use schema::{Column, Schema};
 use sqlite3_parser::{ast, ast::Cmd, lexer::sql::Parser};
+use std::borrow::Cow;
 use std::cell::Cell;
 use std::collections::HashMap;
 use std::num::NonZero;
@@ -485,8 +486,12 @@ impl Statement {
         self.program.result_columns.len()
     }
 
-    pub fn get_column_name(&self, idx: usize) -> Option<&String> {
-        self.program.result_columns[idx].name(&self.program.table_references)
+    pub fn get_column_name(&self, idx: usize) -> Cow<String> {
+        let column = &self.program.result_columns[idx];
+        match column.name(&self.program.table_references) {
+            Some(name) => Cow::Borrowed(name),
+            None => Cow::Owned(column.expr.to_string()),
+        }
     }
 
     pub fn parameters(&self) -> &parameters::Parameters {


Test patch:
diff --git a/tests/integration/common.rs b/tests/integration/common.rs
index 928b33151..521ef8632 100644
--- a/tests/integration/common.rs
+++ b/tests/integration/common.rs
@@ -99,16 +99,16 @@ mod tests {
 
         let columns = stmt.num_columns();
         assert_eq!(columns, 3);
-        assert_eq!(stmt.get_column_name(0), Some(&"foo".to_string()));
-        assert_eq!(stmt.get_column_name(1), Some(&"bar".to_string()));
-        assert_eq!(stmt.get_column_name(2), Some(&"baz".to_string()));
+        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());
 
         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), Some(&"foo".to_string()));
-        assert_eq!(stmt.get_column_name(1), Some(&"bar".to_string()));
+        assert_eq!(stmt.get_column_name(0), "foo".into());
+        assert_eq!(stmt.get_column_name(1), "bar".into());
 
         let stmt = conn.prepare("delete from test;")?;
         let columns = stmt.num_columns();