diff --git a/core/translate/expr.rs b/core/translate/expr.rs index ae5fd8bca..6bd60d2fb 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -2715,11 +2715,6 @@ pub fn sanitize_double_quoted_string(input: &str) -> String { input[1..input.len() - 1].replace("\"\"", "\"").to_string() } -/// Checks if an identifier represents a double-quoted string that should get fallback behavior -pub fn is_double_quoted_identifier(id_str: &str) -> bool { - id_str.len() >= 2 && id_str.starts_with('"') && id_str.ends_with('"') -} - /// Returns the components of a binary expression /// e.g. t.x = 5 -> Some((t.x, =, 5)) pub fn as_binary_components( diff --git a/core/translate/insert.rs b/core/translate/insert.rs index eabf054c9..df2e7471b 100644 --- a/core/translate/insert.rs +++ b/core/translate/insert.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use turso_sqlite3_parser::ast::{ - DistinctNames, Expr, InsertBody, OneSelect, QualifiedName, ResolveType, ResultColumn, With, + self, DistinctNames, Expr, InsertBody, OneSelect, QualifiedName, ResolveType, ResultColumn, + With, }; use crate::error::{SQLITE_CONSTRAINT_NOTNULL, SQLITE_CONSTRAINT_PRIMARYKEY}; @@ -111,6 +112,14 @@ pub fn translate_insert( } let mut param_idx = 1; for expr in values_expr.iter_mut().flat_map(|v| v.iter_mut()) { + if let Expr::Id(name) = expr { + if name.is_double_quoted() { + *expr = Expr::Literal(ast::Literal::String(format!("{name}"))); + } else { + // an INSERT INTO ... VALUES (...) cannot reference columns + crate::bail_parse_error!("no such column: {name}"); + } + } rewrite_expr(expr, &mut param_idx)?; } values = values_expr.pop(); diff --git a/core/translate/optimizer/mod.rs b/core/translate/optimizer/mod.rs index f69272f8d..9b84cd9f1 100644 --- a/core/translate/optimizer/mod.rs +++ b/core/translate/optimizer/mod.rs @@ -14,9 +14,8 @@ use crate::{ parameters::PARAM_PREFIX, schema::{Index, IndexColumn, Schema, Table}, translate::{ - expr::is_double_quoted_identifier, expr::walk_expr_mut, - optimizer::access_method::AccessMethodParams, optimizer::constraints::TableConstraints, - plan::Scan, plan::TerminationKey, + expr::walk_expr_mut, optimizer::access_method::AccessMethodParams, + optimizer::constraints::TableConstraints, plan::Scan, plan::TerminationKey, }, types::SeekOp, LimboError, Result, @@ -701,7 +700,7 @@ impl Optimizable for ast::Expr { Expr::FunctionCallStar { .. } => false, Expr::Id(id) => { // If we got here with an id, this has to be double-quotes identifier - assert!(is_double_quoted_identifier(id.as_str())); + assert!(id.is_double_quoted()); true } Expr::Column { .. } => false, diff --git a/core/translate/planner.rs b/core/translate/planner.rs index ee99b5c53..905ed8b90 100644 --- a/core/translate/planner.rs +++ b/core/translate/planner.rs @@ -205,7 +205,7 @@ pub fn bind_column_references( } // SQLite behavior: Only double-quoted identifiers get fallback to string literals // Single quotes are handled as literals earlier, unquoted identifiers must resolve to columns - if crate::translate::expr::is_double_quoted_identifier(id.as_str()) { + if id.is_double_quoted() { // Convert failed double-quoted identifier to string literal *expr = Expr::Literal(Literal::String(id.as_str().to_string())); Ok(()) diff --git a/testing/insert.test b/testing/insert.test index 78f881094..ff5bf286a 100755 --- a/testing/insert.test +++ b/testing/insert.test @@ -1,6 +1,7 @@ #!/usr/bin/env tclsh set testdir [file dirname $argv0] source $testdir/tester.tcl +source $testdir/sqlite3/tester.tcl do_execsql_test_on_specific_db {:memory:} basic-insert { create table temp (t1 integer, primary key (t1)); @@ -582,3 +583,15 @@ do_execsql_test_on_specific_db {:memory:} returning-null-values { CREATE TABLE test (id INTEGER, name TEXT, value INTEGER); INSERT INTO test (id, name, value) VALUES (1, NULL, NULL) RETURNING id, name, value; } {1||} + +do_catchsql_test unknown-identifier-in-values-clause { + DROP TABLE IF EXISTS tt; + CREATE TABLE tt (x); + INSERT INTO tt VALUES(asdf); +} {1 {no such column: asdf}} + +do_catchsql_test unknown-backtick-identifier-in-values-clause { + DROP TABLE IF EXISTS tt; + CREATE TABLE tt (x); + INSERT INTO tt VALUES(`asdf`); +} {1 {no such column: `asdf`}} diff --git a/vendored/sqlite3-parser/src/parser/ast/mod.rs b/vendored/sqlite3-parser/src/parser/ast/mod.rs index 605840f31..088b6ab03 100644 --- a/vendored/sqlite3-parser/src/parser/ast/mod.rs +++ b/vendored/sqlite3-parser/src/parser/ast/mod.rs @@ -1178,6 +1178,14 @@ impl Name { _ => Name::Ident(s.to_string()), } } + + /// Checks if a name represents a double-quoted string that should get fallback behavior + pub fn is_double_quoted(&self) -> bool { + if let Self::Quoted(ident) = self { + return ident.starts_with("\""); + } + false + } } struct QuotedIterator<'s>(Bytes<'s>, u8);