From 726bc24e7881e4bcf81c8daf9179070bc236d236 Mon Sep 17 00:00:00 2001 From: Jussi Saurio Date: Wed, 24 Sep 2025 09:17:28 +0300 Subject: [PATCH] Support referring to rowid as _rowid_ or oid --- core/schema.rs | 3 ++- core/translate/emitter.rs | 6 +++++- core/translate/insert.rs | 13 ++++++++----- core/translate/planner.rs | 8 ++++++-- core/translate/upsert.rs | 5 +++-- testing/select.test | 22 +++++++++++++++++++++- 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/core/schema.rs b/core/schema.rs index 911b1d814..7ab3a4cb7 100644 --- a/core/schema.rs +++ b/core/schema.rs @@ -1,6 +1,7 @@ use crate::function::Func; use crate::incremental::view::IncrementalView; use crate::translate::expr::{bind_and_rewrite_expr, walk_expr, ParamState, WalkControl}; +use crate::translate::planner::ROWID_STRS; use parking_lot::RwLock; /// Simple view structure for non-materialized views @@ -1792,7 +1793,7 @@ impl Index { // Unqualified identifier: must be a column of the target table or ROWID Expr::Id(Name::Ident(n)) | Expr::Id(Name::Quoted(n)) => { let n = n.as_str(); - if !n.eq_ignore_ascii_case("rowid") && !has_col(n) { + if !ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(n)) && !has_col(n) { ok = false; } } diff --git a/core/translate/emitter.rs b/core/translate/emitter.rs index 932f6ecf4..fb5f35a24 100644 --- a/core/translate/emitter.rs +++ b/core/translate/emitter.rs @@ -30,6 +30,7 @@ use crate::translate::expr::{ ReturningValueRegisters, WalkControl, }; use crate::translate::plan::{DeletePlan, JoinedTable, Plan, QueryDestination, Search}; +use crate::translate::planner::ROWID_STRS; use crate::translate::result_row::try_fold_expr_to_i64; use crate::translate::values::emit_values; use crate::translate::window::{emit_window_results, init_window, WindowMetadata}; @@ -1817,7 +1818,10 @@ fn rewrite_where_for_update_registers( } Expr::Id(ast::Name::Ident(name)) | Expr::Id(ast::Name::Quoted(name)) => { let normalized = normalize_ident(name.as_str()); - if normalized.eq_ignore_ascii_case("rowid") { + if ROWID_STRS + .iter() + .any(|s| s.eq_ignore_ascii_case(&normalized)) + { *e = Expr::Register(rowid_reg); } else if let Some((idx, c)) = columns.iter().enumerate().find(|(_, c)| { c.name diff --git a/core/translate/insert.rs b/core/translate/insert.rs index f760a8106..8c30a7fbb 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -17,7 +17,7 @@ use crate::translate::expr::{ ParamState, ReturningValueRegisters, WalkControl, }; use crate::translate::plan::TableReferences; -use crate::translate::planner::ROWID; +use crate::translate::planner::ROWID_STRS; use crate::translate::upsert::{ collect_set_clauses_for_upsert, emit_upsert, resolve_upsert_target, ResolvedUpsertTarget, }; @@ -1038,8 +1038,8 @@ impl InsertionKey<'_> { .as_ref() .expect("rowid alias column must be present") .as_str(), - InsertionKey::LiteralRowid { .. } => ROWID, - InsertionKey::Autogenerated { .. } => ROWID, + InsertionKey::LiteralRowid { .. } => ROWID_STRS[0], + InsertionKey::Autogenerated { .. } => ROWID_STRS[0], } } } @@ -1128,7 +1128,10 @@ fn build_insertion<'a>( } else { column_mappings[idx_in_table].value_index = Some(value_index); } - } else if column_name == ROWID { + } else if ROWID_STRS + .iter() + .any(|s| s.eq_ignore_ascii_case(&column_name)) + { // Explicit use of the 'rowid' keyword if let Some(col_in_table) = table.columns().iter().find(|c| c.is_rowid_alias) { insertion_key = InsertionKey::RowidAlias(ColMapping { @@ -1406,7 +1409,7 @@ pub fn rewrite_partial_index_where( insertion: &Insertion, ) -> crate::Result { let col_reg = |name: &str| -> Option { - if name.eq_ignore_ascii_case("rowid") { + if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(name)) { Some(insertion.key_register()) } else if let Some(c) = insertion.get_col_mapping_by_name(name) { if c.column.is_rowid_alias { diff --git a/core/translate/planner.rs b/core/translate/planner.rs index e05498e40..6b739b0e3 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -30,7 +30,8 @@ use turso_parser::ast::{ self, As, Expr, FromClause, JoinType, Materialized, Over, QualifiedName, TableInternalId, With, }; -pub const ROWID: &str = "rowid"; +/// Valid ways to refer to the rowid of a btree table. +pub const ROWID_STRS: [&str; 3] = ["rowid", "_rowid_", "oid"]; /// This function walks the expression tree and identifies aggregate /// and window functions. @@ -1094,7 +1095,10 @@ pub fn parse_row_id( where F: FnOnce() -> bool, { - if column_name.eq_ignore_ascii_case(ROWID) { + if ROWID_STRS + .iter() + .any(|s| s.eq_ignore_ascii_case(column_name)) + { if fn_check() { crate::bail_parse_error!("ROWID is ambiguous"); } diff --git a/core/translate/upsert.rs b/core/translate/upsert.rs index 53493c4b9..9755802c8 100644 --- a/core/translate/upsert.rs +++ b/core/translate/upsert.rs @@ -7,6 +7,7 @@ use turso_parser::ast::{self, Upsert}; use crate::error::SQLITE_CONSTRAINT_PRIMARYKEY; use crate::translate::expr::{walk_expr, WalkControl}; use crate::translate::insert::format_unique_violation_desc; +use crate::translate::planner::ROWID_STRS; use crate::vdbe::insn::CmpInsFlags; use crate::{ bail_parse_error, @@ -895,7 +896,7 @@ fn rewrite_expr_to_registers( // Map a column name to a register within the row image at `base_start`. let col_reg_from_row_image = |name: &str| -> Option { - if name.eq_ignore_ascii_case("rowid") { + if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(name)) { return Some(rowid_reg); } let (idx, c) = table.get_column_by_name(name)?; @@ -917,7 +918,7 @@ fn rewrite_expr_to_registers( // Handle EXCLUDED.* if enabled if allow_excluded && ns.eq_ignore_ascii_case("excluded") { if let Some(ins) = insertion { - if c.eq_ignore_ascii_case("rowid") { + if ROWID_STRS.iter().any(|s| s.eq_ignore_ascii_case(&c)) { *expr = Expr::Register(ins.key_register()); } else if let Some(cm) = ins.get_col_mapping_by_name(&c) { *expr = Expr::Register(cm.register); diff --git a/testing/select.test b/testing/select.test index 6efc3e061..bfa214e81 100755 --- a/testing/select.test +++ b/testing/select.test @@ -766,4 +766,24 @@ foreach {testname limit ans} { } { do_execsql_test limit-complex-exprs-$testname \ "SELECT id FROM users ORDER BY id LIMIT $limit" $ans -} \ No newline at end of file +} + +do_execsql_test_on_specific_db {:memory:} rowid-references { + CREATE TABLE test_table (id INTEGER); + INSERT INTO test_table VALUES (5),(5); + SELECT + rowid, "rowid", `rowid`, [rowid], _rowid_, "_rowid_", `_rowid_`, [_rowid_], oid, "oid", `oid`, [oid] + FROM test_table + WHERE rowid = 2 + AND "rowid" = 2 + AND `rowid` = 2 + AND [rowid] = 2 + AND _rowid_ = 2 + AND "_rowid_" = 2 + AND `_rowid_` = 2 + AND [_rowid_] = 2 + AND oid = 2 + AND "oid" = 2 + AND `oid` = 2 + AND [oid] = 2; +} {2|2|2|2|2|2|2|2|2|2|2|2} \ No newline at end of file