From 9bd852297a6cefe398ce081e505faebaef0cf80b Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 26 Sep 2025 17:31:02 -0400 Subject: [PATCH 1/6] Allow in parser using `rowid` explicitly for a col when creating table --- parser/src/parser.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/parser/src/parser.rs b/parser/src/parser.rs index e39e0ca13..aa756b03e 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -3446,10 +3446,6 @@ impl<'a> Parser<'a> { pub fn parse_column_definition(&mut self, in_alter: bool) -> Result { let col_name = self.parse_nm()?; - if !in_alter && col_name.as_str().eq_ignore_ascii_case("rowid") { - return Err(Error::Custom("cannot use reserved word: ROWID".to_owned())); - } - let col_type = self.parse_type()?; let constraints = self.parse_named_column_constraints(in_alter)?; Ok(ColumnDefinition { @@ -4039,7 +4035,6 @@ mod tests { "ALTER TABLE my_table ADD COLUMN my_column PRIMARY KEY", "ALTER TABLE my_table ADD COLUMN my_column UNIQUE", "CREATE TEMP TABLE baz.foo(bar)", - "CREATE TABLE foo(rowid)", "CREATE TABLE foo(d INT AS (a*abs(b)))", "CREATE TABLE foo(d INT AS (a*abs(b)))", "CREATE TABLE foo(bar UNKNOWN_INT) STRICT", From af215c2906bd2fdaae67e1a77c168f725b34e64c Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 26 Sep 2025 17:32:16 -0400 Subject: [PATCH 2/6] Check cols first before falling back to explicit rowid in UPDATE translation --- core/translate/update.rs | 60 +++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/core/translate/update.rs b/core/translate/update.rs index ab035f377..e045fa329 100644 --- a/core/translate/update.rs +++ b/core/translate/update.rs @@ -228,36 +228,40 @@ pub fn prepare_update_plan( for (col_name, expr) in set.col_names.iter().zip(values.iter()) { let ident = normalize_ident(col_name.as_str()); - // Check if this is the 'rowid' keyword - if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&ident)) { - // Find the rowid alias column if it exists - if let Some((idx, _col)) = table - .columns() - .iter() - .enumerate() - .find(|(_, c)| c.is_rowid_alias) - { - // Use the rowid alias column index - match set_clauses.iter_mut().find(|(i, _)| i == &idx) { - Some((_, existing_expr)) => *existing_expr = expr.clone(), - None => set_clauses.push((idx, expr.clone())), - } - } else { - // No rowid alias, use sentinel value for actual rowid - match set_clauses.iter_mut().find(|(i, _)| *i == ROWID_SENTINEL) { - Some((_, existing_expr)) => *existing_expr = expr.clone(), - None => set_clauses.push((ROWID_SENTINEL, expr.clone())), + let col_index = match column_lookup.get(&ident) { + Some(idx) => *idx, + None => { + // Check if this is the 'rowid' keyword + if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&ident)) { + // Find the rowid alias column if it exists + if let Some((idx, _col)) = table + .columns() + .iter() + .enumerate() + .find(|(_i, c)| c.is_rowid_alias) + { + // Use the rowid alias column index + match set_clauses.iter_mut().find(|(i, _)| i == &idx) { + Some((_, existing_expr)) => *existing_expr = expr.clone(), + None => set_clauses.push((idx, expr.clone())), + } + idx + } else { + // No rowid alias, use sentinel value for actual rowid + match set_clauses.iter_mut().find(|(i, _)| *i == ROWID_SENTINEL) { + Some((_, existing_expr)) => *existing_expr = expr.clone(), + None => set_clauses.push((ROWID_SENTINEL, expr.clone())), + } + ROWID_SENTINEL + } + } else { + crate::bail_parse_error!("no such column: {}.{}", table_name, col_name); } } - } else { - let col_index = match column_lookup.get(&ident) { - Some(idx) => idx, - None => bail_parse_error!("no such column: {}", ident), - }; - match set_clauses.iter_mut().find(|(idx, _)| idx == col_index) { - Some((_, existing_expr)) => *existing_expr = expr.clone(), - None => set_clauses.push((*col_index, expr.clone())), - } + }; + match set_clauses.iter_mut().find(|(idx, _)| *idx == col_index) { + Some((_, existing_expr)) => *existing_expr = expr.clone(), + None => set_clauses.push((col_index, expr.clone())), } } } From e52aa1538e10f0d13783881073c2ece64d1b9e29 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 26 Sep 2025 17:32:51 -0400 Subject: [PATCH 3/6] Remove unused BTreeTable method for checking single field on Column in schema --- core/schema.rs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/core/schema.rs b/core/schema.rs index 13bd620fb..b1aece65a 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -976,17 +976,13 @@ impl BTreeTable { pub fn get_rowid_alias_column(&self) -> Option<(usize, &Column)> { if self.primary_key_columns.len() == 1 { let (idx, col) = self.get_column(&self.primary_key_columns[0].0)?; - if self.column_is_rowid_alias(col) { + if col.is_rowid_alias { return Some((idx, col)); } } None } - pub fn column_is_rowid_alias(&self, col: &Column) -> bool { - col.is_rowid_alias - } - /// Returns the column position and column for a given column name. /// Returns None if the column name is not found. /// E.g. if table is CREATE TABLE t (a, b, c) @@ -2027,7 +2023,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !table.column_is_rowid_alias(column), + !column.is_rowid_alias, "column 'a´ has type different than INTEGER so can't be a rowid alias" ); Ok(()) @@ -2038,10 +2034,7 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT);"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!( - table.column_is_rowid_alias(column), - "column 'a´ should be a rowid alias" - ); + assert!(column.is_rowid_alias, "column 'a´ should be a rowid alias"); Ok(()) } @@ -2051,10 +2044,7 @@ mod tests { let sql = r#"CREATE TABLE t1 (a INTEGER, b TEXT, PRIMARY KEY(a));"#; let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; - assert!( - table.column_is_rowid_alias(column), - "column 'a´ should be a rowid alias" - ); + assert!(column.is_rowid_alias, "column 'a´ should be a rowid alias"); Ok(()) } @@ -2065,7 +2055,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !table.column_is_rowid_alias(column), + !column, "column 'a´ shouldn't be a rowid alias because table has no rowid" ); Ok(()) @@ -2077,7 +2067,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !table.column_is_rowid_alias(column), + !column.is_rowid_alias, "column 'a´ shouldn't be a rowid alias because table has no rowid" ); Ok(()) @@ -2100,7 +2090,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !table.column_is_rowid_alias(column), + !column.is_rowid_alias, "column 'a´ shouldn't be a rowid alias because table has composite primary key" ); Ok(()) From d4dc45832872fddd1f941fa9205dde4008ae8629 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 26 Sep 2025 17:33:38 -0400 Subject: [PATCH 4/6] Evaluate table column refs before checking `rowid` to allow using it as col name --- core/translate/expr.rs | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/core/translate/expr.rs b/core/translate/expr.rs index fd6fbebce..c15296418 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -3302,12 +3302,6 @@ pub fn bind_and_rewrite_expr<'a>( top_level_expr, &mut |expr: &mut ast::Expr| -> Result { match expr { - ast::Expr::Id(ast::Name::Ident(n)) if n.eq_ignore_ascii_case("true") => { - *expr = ast::Expr::Literal(ast::Literal::Numeric("1".to_string())); - } - ast::Expr::Id(ast::Name::Ident(n)) if n.eq_ignore_ascii_case("false") => { - *expr = ast::Expr::Literal(ast::Literal::Numeric("0".to_string())); - } // Rewrite anonymous variables in encounter order. ast::Expr::Variable(var) if var.is_empty() => { if !param_state.is_valid() { @@ -3370,17 +3364,6 @@ pub fn bind_and_rewrite_expr<'a>( } } } - if !referenced_tables.joined_tables().is_empty() { - if let Some(row_id_expr) = parse_row_id( - &normalized_id, - referenced_tables.joined_tables()[0].internal_id, - || referenced_tables.joined_tables().len() != 1, - )? { - *expr = row_id_expr; - - return Ok(WalkControl::Continue); - } - } let mut match_result = None; // First check joined tables @@ -3416,6 +3399,15 @@ pub fn bind_and_rewrite_expr<'a>( col_idx.unwrap(), col.is_rowid_alias, )); + // only if we haven't found a match, check for explicit rowid reference + } else if let Some(row_id_expr) = parse_row_id( + &normalized_id, + referenced_tables.joined_tables()[0].internal_id, + || referenced_tables.joined_tables().len() != 1, + )? { + *expr = row_id_expr; + + return Ok(WalkControl::Continue); } } @@ -3496,17 +3488,16 @@ pub fn bind_and_rewrite_expr<'a>( } let (tbl_id, tbl) = matching_tbl.unwrap(); let normalized_id = normalize_ident(id.as_str()); - - if let Some(row_id_expr) = parse_row_id(&normalized_id, tbl_id, || false)? { - *expr = row_id_expr; - - return Ok(WalkControl::Continue); - } let col_idx = tbl.columns().iter().position(|c| { c.name .as_ref() .is_some_and(|name| name.eq_ignore_ascii_case(&normalized_id)) }); + if let Some(row_id_expr) = parse_row_id(&normalized_id, tbl_id, || false)? { + *expr = row_id_expr; + + return Ok(WalkControl::Continue); + } let Some(col_idx) = col_idx else { crate::bail_parse_error!("no such column: {}", normalized_id); }; From abab04dac92917c18ee08d5283f8222869a39b63 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 26 Sep 2025 17:33:53 -0400 Subject: [PATCH 5/6] Add regression test for col named rowid --- testing/create_table.test | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/testing/create_table.test b/testing/create_table.test index f5fceba67..7eb7bea7d 100755 --- a/testing/create_table.test +++ b/testing/create_table.test @@ -45,3 +45,11 @@ do_execsql_test_in_memory_any_error create_table_column_and_table_primary_keys { do_execsql_test_in_memory_any_error create_table_multiple_table_primary_keys { CREATE TABLE t(a,b,c,d,primary key(a,b), primary key(c,d)); } + +# https://github.com/tursodatabase/turso/issues/3282 +do_execsql_test_on_specific_db {:memory:} col-named-rowid { + create table t(rowid, a); + insert into t values (1,2), (2,3), (3,4); + update t set rowid = 1; -- should allow regular update and not throw unique constraint + select count(*) from t where rowid = 1; +} {3} From 5d8a735aafa7f5924e03ab9d3e16ccc8f608b57e Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Fri, 26 Sep 2025 18:06:09 -0400 Subject: [PATCH 6/6] fix clippy error --- core/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/schema.rs b/core/schema.rs index b1aece65a..20ae6380e 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -2055,7 +2055,7 @@ mod tests { let table = BTreeTable::from_sql(sql, 0)?; let column = table.get_column("a").unwrap().1; assert!( - !column, + !column.is_rowid_alias, "column 'a´ shouldn't be a rowid alias because table has no rowid" ); Ok(())