feat: support keyword rowid

This commit is contained in:
Kould
2025-01-03 01:42:36 +08:00
parent 55e79a72c1
commit 5305a9d0fd
7 changed files with 111 additions and 26 deletions

View File

@@ -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!(),

View File

@@ -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<F>(column_name: &str, table_id: usize, fn_check: F) -> Result<Option<ast::Expr>>
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)
}

View File

@@ -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}

View File

@@ -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"}

View File

@@ -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;

View File

@@ -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)?;

View File

@@ -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<usize>,
/// the y in `x.y.z`. index of the table in catalog.
table: usize,
},
/// `IN`
InList {
/// expression