From ae24d637a8f6806550884bd6bd6ca153029bca8a Mon Sep 17 00:00:00 2001 From: Nikita Sivukhin Date: Fri, 26 Sep 2025 11:34:27 +0400 Subject: [PATCH] adjust edge-cases --- core/translate/expr.rs | 10 ++-------- core/translate/insert.rs | 6 ++---- core/translate/schema.rs | 2 +- core/vdbe/execute.rs | 2 +- parser/src/ast.rs | 12 ++++++++++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/translate/expr.rs b/core/translate/expr.rs index bfd6e4eb3..c1a279bfd 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -1826,7 +1826,7 @@ pub fn translate_expr( ast::Expr::Id(id) => { // Treat double-quoted identifiers as string literals (SQLite compatibility) program.emit_insn(Insn::String8 { - value: sanitize_double_quoted_string(id.as_str()), + value: id.as_str().to_string(), dest: target_register, }); Ok(target_register) @@ -3008,12 +3008,6 @@ pub fn sanitize_string(input: &str) -> String { inner.replace("''", "'") } -/// Sanitizes a double-quoted string literal by removing double quotes at front and back -/// and unescaping double quotes -pub fn sanitize_double_quoted_string(input: &str) -> String { - input[1..input.len() - 1].replace("\"\"", "\"").to_string() -} - /// Returns the components of a binary expression /// e.g. t.x = 5 -> Some((t.x, =, 5)) pub fn as_binary_components( @@ -3473,7 +3467,7 @@ pub fn bind_and_rewrite_expr<'a>( // Single quotes are handled as literals earlier, unquoted identifiers must resolve to columns if id.quoted_with('"') { // Convert failed double-quoted identifier to string literal - *expr = Expr::Literal(ast::Literal::String(id.as_str().to_string())); + *expr = Expr::Literal(ast::Literal::String(id.as_literal())); return Ok(WalkControl::Continue); } else { // Unquoted identifiers must resolve to columns - no fallback diff --git a/core/translate/insert.rs b/core/translate/insert.rs index c8e2627a1..6bd9c2150 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -149,10 +149,8 @@ pub fn translate_insert( match expr.as_mut() { Expr::Id(name) => { if name.quoted_with('"') { - *expr = Expr::Literal(ast::Literal::String( - name.as_literal().to_string(), - )) - .into(); + *expr = + Expr::Literal(ast::Literal::String(name.as_literal())).into(); } else { // an INSERT INTO ... VALUES (...) cannot reference columns crate::bail_parse_error!("no such column: {name}"); diff --git a/core/translate/schema.rs b/core/translate/schema.rs index c4f705113..c3dc5b391 100644 --- a/core/translate/schema.rs +++ b/core/translate/schema.rs @@ -422,7 +422,7 @@ fn collect_autoindexes( fn create_table_body_to_str(tbl_name: &ast::QualifiedName, body: &ast::CreateTableBody) -> String { let mut sql = String::new(); - sql.push_str(format!("CREATE TABLE {} {}", tbl_name.name.as_str(), body).as_str()); + sql.push_str(format!("CREATE TABLE {} {}", tbl_name.name.as_quoted(), body).as_str()); match body { ast::CreateTableBody::ColumnsAndConstraints { columns: _, diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 0240c656e..4a88a1e14 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -5365,7 +5365,7 @@ pub fn op_function( let column = columns .iter_mut() .find(|column| { - column.col_name == ast::Name::exact(original_rename_from.to_string()) + column.col_name.as_str() == original_rename_from.as_str() }) .expect("column being renamed should be present"); diff --git a/parser/src/ast.rs b/parser/src/ast.rs index adfabe00b..379124ac6 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -924,6 +924,8 @@ impl<'de> serde::Deserialize<'de> for Name { } impl Name { + /// Create name which will have exactly the value of given string + /// (e.g. if s = "\"str\"" - the name value will contain quotes and translation to SQL will give us """str""") pub fn exact(s: String) -> Self { let value_is_lowercase = s.chars().all(|x| x.is_lowercase()); Self { @@ -933,9 +935,11 @@ impl Name { value_is_lowercase, } } + /// Parse name from the bytes (e.g. handle quoting and handle escaped quotes) pub fn from_bytes(s: &[u8]) -> Self { Self::from_str(unsafe { std::str::from_utf8_unchecked(s) }) } + /// Parse name from the string (e.g. handle quoting and handle escaped quotes) pub fn from_str(s: impl AsRef) -> Self { let s = s.as_ref(); let bytes = s.as_bytes(); @@ -969,6 +973,8 @@ impl Name { } } + /// Return string value of the name alredy converted to the lowercase + /// This value can be safely compared with other values without any normalization logic pub fn as_str(&self) -> &str { if self.value_is_lowercase { return &self.value; @@ -979,10 +985,12 @@ impl Name { self.lowercase.get().unwrap() } - pub fn as_literal(&self) -> &str { - &self.value + /// Convert value to the string literal (e.g. single-quoted string with escaped single quotes) + pub fn as_literal(&self) -> String { + format!("'{}'", self.value.replace("'", "''")) } + /// Convert value to the name string (e.g. double-quoted string with escaped double quotes) pub fn as_quoted(&self) -> String { let value = self.value.as_bytes(); if !value.is_empty() && value.iter().all(|x| x.is_ascii_alphanumeric()) {