SQLite: Allow dollar signs in placeholder names (#1620)

This commit is contained in:
Hans Ott 2024-12-28 14:20:48 +01:00 committed by GitHub
parent d0d4153137
commit 48f025f658
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 53 additions and 4 deletions

View file

@ -636,6 +636,12 @@ pub trait Dialect: Debug + Any {
false false
} }
/// Returns true if this dialect allows dollar placeholders
/// e.g. `SELECT $var` (SQLite)
fn supports_dollar_placeholder(&self) -> bool {
false
}
/// Does the dialect support with clause in create index statement? /// Does the dialect support with clause in create index statement?
/// e.g. `CREATE INDEX idx ON t WITH (key = value, key2)` /// e.g. `CREATE INDEX idx ON t WITH (key = value, key2)`
fn supports_create_index_with_clause(&self) -> bool { fn supports_create_index_with_clause(&self) -> bool {

View file

@ -81,4 +81,8 @@ impl Dialect for SQLiteDialect {
fn supports_asc_desc_in_column_definition(&self) -> bool { fn supports_asc_desc_in_column_definition(&self) -> bool {
true true
} }
fn supports_dollar_placeholder(&self) -> bool {
true
}
} }

View file

@ -1509,7 +1509,8 @@ impl<'a> Tokenizer<'a> {
chars.next(); chars.next();
if let Some('$') = chars.peek() { // If the dialect does not support dollar-quoted strings, then `$$` is rather a placeholder.
if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
chars.next(); chars.next();
let mut is_terminated = false; let mut is_terminated = false;
@ -1543,10 +1544,14 @@ impl<'a> Tokenizer<'a> {
}; };
} else { } else {
value.push_str(&peeking_take_while(chars, |ch| { value.push_str(&peeking_take_while(chars, |ch| {
ch.is_alphanumeric() || ch == '_' ch.is_alphanumeric()
|| ch == '_'
// Allow $ as a placeholder character if the dialect supports it
|| matches!(ch, '$' if self.dialect.supports_dollar_placeholder())
})); }));
if let Some('$') = chars.peek() { // If the dialect does not support dollar-quoted strings, don't look for the end delimiter.
if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
chars.next(); chars.next();
'searching_for_end: loop { 'searching_for_end: loop {
@ -2137,7 +2142,7 @@ fn take_char_from_hex_digits(
mod tests { mod tests {
use super::*; use super::*;
use crate::dialect::{ use crate::dialect::{
BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect, BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect, SQLiteDialect,
}; };
use core::fmt::Debug; use core::fmt::Debug;
@ -2573,6 +2578,30 @@ mod tests {
); );
} }
#[test]
fn tokenize_dollar_placeholder() {
let sql = String::from("SELECT $$, $$ABC$$, $ABC$, $ABC");
let dialect = SQLiteDialect {};
let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
assert_eq!(
tokens,
vec![
Token::make_keyword("SELECT"),
Token::Whitespace(Whitespace::Space),
Token::Placeholder("$$".into()),
Token::Comma,
Token::Whitespace(Whitespace::Space),
Token::Placeholder("$$ABC$$".into()),
Token::Comma,
Token::Whitespace(Whitespace::Space),
Token::Placeholder("$ABC$".into()),
Token::Comma,
Token::Whitespace(Whitespace::Space),
Token::Placeholder("$ABC".into()),
]
);
}
#[test] #[test]
fn tokenize_dollar_quoted_string_untagged() { fn tokenize_dollar_quoted_string_untagged() {
let sql = let sql =

View file

@ -561,6 +561,16 @@ fn test_dollar_identifier_as_placeholder() {
} }
_ => unreachable!(), _ => unreachable!(),
} }
// $$ is a valid placeholder in SQLite
match sqlite().verified_expr("id = $$") {
Expr::BinaryOp { op, left, right } => {
assert_eq!(op, BinaryOperator::Eq);
assert_eq!(left, Box::new(Expr::Identifier(Ident::new("id"))));
assert_eq!(right, Box::new(Expr::Value(Placeholder("$$".to_string()))));
}
_ => unreachable!(),
}
} }
fn sqlite() -> TestedDialects { fn sqlite() -> TestedDialects {