From 5305a9d0fdeed927eeadd29fa9966968c66b6adc Mon Sep 17 00:00:00 2001 From: Kould Date: Fri, 3 Jan 2025 01:42:36 +0800 Subject: [PATCH] feat: support keyword `rowid` --- core/translate/expr.rs | 9 ++ core/translate/planner.rs | 93 +++++++++++++------ testing/join.test | 16 ++++ testing/select.test | 8 ++ .../sqlite3-parser/src/parser/ast/check.rs | 3 + vendored/sqlite3-parser/src/parser/ast/fmt.rs | 1 + vendored/sqlite3-parser/src/parser/ast/mod.rs | 7 ++ 7 files changed, 111 insertions(+), 26 deletions(-) diff --git a/core/translate/expr.rs b/core/translate/expr.rs index d13902433..965326131 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1545,6 +1545,15 @@ pub fn translate_expr( } } } + ast::Expr::RowId { database: _, table } => { + let tbl_ref = referenced_tables.as_ref().unwrap().get(*table).unwrap(); + let cursor_id = program.resolve_cursor_id(&tbl_ref.table_identifier); + program.emit_insn(Insn::RowId { + cursor_id, + dest: target_register, + }); + Ok(target_register) + } ast::Expr::InList { .. } => todo!(), ast::Expr::InSelect { .. } => todo!(), ast::Expr::InTable { .. } => todo!(), diff --git a/core/translate/planner.rs b/core/translate/planner.rs index f5c835f8e..998145263 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -12,6 +12,8 @@ use crate::{ }; use sqlite3_parser::ast::{self, Expr, FromClause, JoinType, Limit}; +pub const ROWID: &'static str = "rowid"; + pub struct OperatorIdCounter { id: usize, } @@ -102,8 +104,18 @@ pub fn bind_column_references( if id.0.eq_ignore_ascii_case("true") || id.0.eq_ignore_ascii_case("false") { return Ok(()); } - let mut match_result = None; let normalized_id = normalize_ident(id.0.as_str()); + + if referenced_tables.len() > 0 { + if let Some(row_id_expr) = + parse_row_id(&normalized_id, 0, || referenced_tables.len() != 1)? + { + *expr = row_id_expr; + + return Ok(()); + } + } + let mut match_result = None; for (tbl_idx, table) in referenced_tables.iter().enumerate() { let col_idx = table .columns() @@ -140,6 +152,12 @@ pub fn bind_column_references( } let tbl_idx = matching_tbl_idx.unwrap(); let normalized_id = normalize_ident(id.0.as_str()); + + if let Some(row_id_expr) = parse_row_id(&normalized_id, tbl_idx, || false)? { + *expr = row_id_expr; + + return Ok(()); + } let col_idx = referenced_tables[tbl_idx] .columns() .iter() @@ -209,7 +227,7 @@ pub fn bind_column_references( Ok(()) } // Already bound earlier - ast::Expr::Column { .. } => Ok(()), + ast::Expr::Column { .. } | ast::Expr::RowId { .. } => Ok(()), ast::Expr::DoublyQualified(_, _, _) => todo!(), ast::Expr::Exists(_) => todo!(), ast::Expr::FunctionCallStar { .. } => Ok(()), @@ -491,17 +509,23 @@ fn parse_join( let left_tables = &tables[..table_index]; assert!(!left_tables.is_empty()); let right_table = &tables[table_index]; - let mut left_col = None; + let mut left_col = + parse_row_id(&name_normalized, 0, || left_tables.len() != 1)?; for (left_table_idx, left_table) in left_tables.iter().enumerate() { + if left_col.is_some() { + break; + } left_col = left_table .columns() .iter() .enumerate() .find(|(_, col)| col.name == name_normalized) - .map(|(idx, col)| (left_table_idx, idx, col)); - if left_col.is_some() { - break; - } + .map(|(idx, col)| ast::Expr::Column { + database: None, + table: left_table_idx, + column: idx, + is_rowid_alias: col.is_rowid_alias, + }); } if left_col.is_none() { crate::bail_parse_error!( @@ -509,33 +533,33 @@ fn parse_join( distinct_name.0 ); } - let right_col = right_table - .columns() - .iter() - .enumerate() - .find(|(_, col)| col.name == name_normalized); + let right_col = + parse_row_id(&name_normalized, right_table.table_index, || false)?.or_else( + || { + right_table + .table + .columns() + .iter() + .enumerate() + .find(|(_, col)| col.name == name_normalized) + .map(|(i, col)| ast::Expr::Column { + database: None, + table: right_table.table_index, + column: i, + is_rowid_alias: col.is_rowid_alias, + }) + }, + ); if right_col.is_none() { crate::bail_parse_error!( "cannot join using column {} - column not present in all tables", distinct_name.0 ); } - let (left_table_idx, left_col_idx, left_col) = left_col.unwrap(); - let (right_col_idx, right_col) = right_col.unwrap(); using_predicates.push(ast::Expr::Binary( - Box::new(ast::Expr::Column { - database: None, - table: left_table_idx, - column: left_col_idx, - is_rowid_alias: left_col.is_rowid_alias, - }), + Box::new(left_col.unwrap()), ast::Operator::Equals, - Box::new(ast::Expr::Column { - database: None, - table: right_table.table_index, - column: right_col_idx, - is_rowid_alias: right_col.is_rowid_alias, - }), + Box::new(right_col.unwrap()), )); } predicates = Some(using_predicates); @@ -582,3 +606,20 @@ pub fn break_predicate_at_and_boundaries( } } } + +fn parse_row_id(column_name: &str, table_id: usize, fn_check: F) -> Result> +where + F: FnOnce() -> bool, +{ + if column_name.eq_ignore_ascii_case(ROWID) { + if fn_check() { + crate::bail_parse_error!("ROWID is ambiguous"); + } + + return Ok(Some(ast::Expr::RowId { + database: None, // TODO: support different databases + table: table_id, + })); + } + Ok(None) +} diff --git a/testing/join.test b/testing/join.test index 7ebcdd6c5..2b6d6c45d 100755 --- a/testing/join.test +++ b/testing/join.test @@ -106,6 +106,22 @@ Jamie|coat Jamie|accessories Cindy|} +do_execsql_test left-join-row-id { + select u.rowid, p.rowid from users u left join products as p on u.rowid = p.rowid where u.rowid >= 10 limit 5; +} {10|10 +11|11 +12| +13| +14|} + +do_execsql_test left-join-row-id-2 { + select u.rowid, p.rowid from users u left join products as p using(rowid) where u.rowid >= 10 limit 5; +} {10|10 +11|11 +12| +13| +14|} + do_execsql_test left-join-constant-condition-true { select u.first_name, p.name from users u left join products as p on true limit 1; } {Jamie|hat} diff --git a/testing/select.test b/testing/select.test index 49f8021bc..9babd104c 100755 --- a/testing/select.test +++ b/testing/select.test @@ -80,6 +80,14 @@ do_execsql_test select_with_quoting_2 { select "users".`id` from users where `users`.[id] = 5; } {5} +do_execsql_test select-rowid { + select rowid, first_name from users u where rowid = 5; +} {5|Edward} + +do_execsql_test select-rowid-2 { + select users.rowid, first_name from users u where rowid = 5; +} {5|Edward} + do_execsql_test seekrowid { select * from users u where u.id = 5; } {"5|Edward|Miller|christiankramer@example.com|725-281-1033|08522 English Plain|Lake Keith|ID|23283|15"} diff --git a/vendored/sqlite3-parser/src/parser/ast/check.rs b/vendored/sqlite3-parser/src/parser/ast/check.rs index 11a3cb031..e1e0eecd3 100644 --- a/vendored/sqlite3-parser/src/parser/ast/check.rs +++ b/vendored/sqlite3-parser/src/parser/ast/check.rs @@ -194,6 +194,9 @@ impl CreateTableBody { { let mut generated_count = 0; for c in columns.values() { + if c.col_name == "rowid" { + return Err(custom_err!("cannot use reserved word: ROWID")); + } for cs in &c.constraints { if let ColumnConstraint::Generated { .. } = cs.constraint { generated_count += 1; diff --git a/vendored/sqlite3-parser/src/parser/ast/fmt.rs b/vendored/sqlite3-parser/src/parser/ast/fmt.rs index 39c929409..34a7fa3f0 100644 --- a/vendored/sqlite3-parser/src/parser/ast/fmt.rs +++ b/vendored/sqlite3-parser/src/parser/ast/fmt.rs @@ -728,6 +728,7 @@ impl ToTokens for Expr { } s.append(TK_RP, None) } + Self::RowId { .. } => Ok(()), Self::Subquery(query) => { s.append(TK_LP, None)?; query.to_tokens(s)?; diff --git a/vendored/sqlite3-parser/src/parser/ast/mod.rs b/vendored/sqlite3-parser/src/parser/ast/mod.rs index 50c7bcacf..466a718ab 100644 --- a/vendored/sqlite3-parser/src/parser/ast/mod.rs +++ b/vendored/sqlite3-parser/src/parser/ast/mod.rs @@ -338,6 +338,13 @@ pub enum Expr { /// is the column a rowid alias is_rowid_alias: bool, }, + /// `ROWID` + RowId { + /// the x in `x.y.z`. index of the db in catalog. + database: Option, + /// the y in `x.y.z`. index of the table in catalog. + table: usize, + }, /// `IN` InList { /// expression