Skip to content

Commit d774caf

Browse files
committed
SQLite: Allow dollar signs in placeholder names
Relevant: apache#1402 SQLite version 3.43.2 2023-10-10 13:08:14 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite> .mode box sqlite> .nullvalue NULL sqlite> SELECT $$, $$ABC$$, $ABC$, $ABC; ┌──────┬─────────┬───────┬──────┐ │ $$ │ $$ABC$$ │ $ABC$ │ $ABC │ ├──────┼─────────┼───────┼──────┤ │ NULL │ NULL │ NULL │ NULL │ └──────┴─────────┴───────┴──────┘
1 parent 4fdeb5c commit d774caf

File tree

4 files changed

+53
-4
lines changed

4 files changed

+53
-4
lines changed

src/dialect/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,12 @@ pub trait Dialect: Debug + Any {
527527
false
528528
}
529529

530+
/// Returns true if this dialect allows dollar placeholders
531+
/// e.g. `SELECT $var` (SQLite)
532+
fn supports_dollar_placeholder(&self) -> bool {
533+
false
534+
}
535+
530536
/// Does the dialect support with clause in create index statement?
531537
/// e.g. `CREATE INDEX idx ON t WITH (key = value, key2)`
532538
fn supports_create_index_with_clause(&self) -> bool {

src/dialect/sqlite.rs

+4
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,8 @@ impl Dialect for SQLiteDialect {
8181
fn supports_asc_desc_in_column_definition(&self) -> bool {
8282
true
8383
}
84+
85+
fn supports_dollar_placeholder(&self) -> bool {
86+
true
87+
}
8488
}

src/tokenizer.rs

+33-4
Original file line numberDiff line numberDiff line change
@@ -1278,7 +1278,8 @@ impl<'a> Tokenizer<'a> {
12781278

12791279
chars.next();
12801280

1281-
if let Some('$') = chars.peek() {
1281+
// If the dialect does not support dollar-quoted strings, then `$$` is rather a placeholder.
1282+
if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
12821283
chars.next();
12831284

12841285
let mut is_terminated = false;
@@ -1312,10 +1313,14 @@ impl<'a> Tokenizer<'a> {
13121313
};
13131314
} else {
13141315
value.push_str(&peeking_take_while(chars, |ch| {
1315-
ch.is_alphanumeric() || ch == '_'
1316+
ch.is_alphanumeric()
1317+
|| ch == '_'
1318+
// Allow $ as a placeholder character if the dialect supports it
1319+
|| matches!(ch, '$' if self.dialect.supports_dollar_placeholder())
13161320
}));
13171321

1318-
if let Some('$') = chars.peek() {
1322+
// If the dialect does not support dollar-quoted strings, don't look for the end delimiter.
1323+
if matches!(chars.peek(), Some('$')) && !self.dialect.supports_dollar_placeholder() {
13191324
chars.next();
13201325

13211326
'searching_for_end: loop {
@@ -1885,7 +1890,7 @@ fn take_char_from_hex_digits(
18851890
mod tests {
18861891
use super::*;
18871892
use crate::dialect::{
1888-
BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect,
1893+
BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect, SQLiteDialect,
18891894
};
18901895
use core::fmt::Debug;
18911896

@@ -2321,6 +2326,30 @@ mod tests {
23212326
);
23222327
}
23232328

2329+
#[test]
2330+
fn tokenize_dollar_placeholder() {
2331+
let sql = String::from("SELECT $$, $$ABC$$, $ABC$, $ABC");
2332+
let dialect = SQLiteDialect {};
2333+
let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap();
2334+
assert_eq!(
2335+
tokens,
2336+
vec![
2337+
Token::make_keyword("SELECT"),
2338+
Token::Whitespace(Whitespace::Space),
2339+
Token::Placeholder("$$".into()),
2340+
Token::Comma,
2341+
Token::Whitespace(Whitespace::Space),
2342+
Token::Placeholder("$$ABC$$".into()),
2343+
Token::Comma,
2344+
Token::Whitespace(Whitespace::Space),
2345+
Token::Placeholder("$ABC$".into()),
2346+
Token::Comma,
2347+
Token::Whitespace(Whitespace::Space),
2348+
Token::Placeholder("$ABC".into()),
2349+
]
2350+
);
2351+
}
2352+
23242353
#[test]
23252354
fn tokenize_dollar_quoted_string_untagged() {
23262355
let sql =

tests/sqlparser_sqlite.rs

+10
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,16 @@ fn test_dollar_identifier_as_placeholder() {
568568
}
569569
_ => unreachable!(),
570570
}
571+
572+
// $$ is a valid placeholder in SQLite
573+
match sqlite().verified_expr("id = $$") {
574+
Expr::BinaryOp { op, left, right } => {
575+
assert_eq!(op, BinaryOperator::Eq);
576+
assert_eq!(left, Box::new(Expr::Identifier(Ident::new("id"))));
577+
assert_eq!(right, Box::new(Expr::Value(Placeholder("$$".to_string()))));
578+
}
579+
_ => unreachable!(),
580+
}
571581
}
572582

573583
fn sqlite() -> TestedDialects {

0 commit comments

Comments
 (0)