handle single, double and unquoted strings in values clause

This commit is contained in:
Mikaël Francoeur
2025-08-07 15:52:05 -04:00
parent 7b4703eba4
commit 2cf4e4fe96
6 changed files with 35 additions and 11 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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