@@ -1664,10 +1664,51 @@ impl<'a> Tokenizer<'a> {
16641664 }
16651665 ':' => {
16661666 chars. next ( ) ;
1667- match chars. peek ( ) {
1668- Some ( ':' ) => self . consume_and_return ( chars, Token :: DoubleColon ) ,
1669- Some ( '=' ) => self . consume_and_return ( chars, Token :: Assignment ) ,
1670- _ => Ok ( Some ( Token :: Colon ) ) ,
1667+ if self . dialect . supports_sqlite_variable_syntax ( ) {
1668+ match chars. peek ( ) . copied ( ) {
1669+ Some ( '=' ) => self . consume_and_return ( chars, Token :: Assignment ) ,
1670+ Some ( c)
1671+ if c. is_ascii_alphanumeric ( )
1672+ || c == '_'
1673+ || c == '$'
1674+ || c >= '\u{0080}' =>
1675+ {
1676+ let body = self . tokenize_sqlite_variable_body ( chars) ?;
1677+ Ok ( Some ( Token :: Placeholder ( format ! ( ":{body}" ) ) ) )
1678+ }
1679+ Some ( ':' ) => {
1680+ chars. next ( ) ; // consume second ':'
1681+ match chars. peek ( ) . copied ( ) {
1682+ Some ( ':' ) => {
1683+ chars. next ( ) ; // consume third ':'
1684+ match chars. peek ( ) . copied ( ) {
1685+ Some ( c)
1686+ if c. is_ascii_alphanumeric ( )
1687+ || c == '_'
1688+ || c == '$'
1689+ || c >= '\u{0080}' =>
1690+ {
1691+ let body =
1692+ self . tokenize_sqlite_variable_body ( chars) ?;
1693+ Ok ( Some ( Token :: Placeholder ( format ! ( ":::{body}" ) ) ) )
1694+ }
1695+ _ => self . tokenizer_error (
1696+ chars. location ( ) ,
1697+ "unexpected ':::' without identifier body" ,
1698+ ) ,
1699+ }
1700+ }
1701+ _ => Ok ( Some ( Token :: DoubleColon ) ) ,
1702+ }
1703+ }
1704+ _ => Ok ( Some ( Token :: Colon ) ) ,
1705+ }
1706+ } else {
1707+ match chars. peek ( ) {
1708+ Some ( ':' ) => self . consume_and_return ( chars, Token :: DoubleColon ) ,
1709+ Some ( '=' ) => self . consume_and_return ( chars, Token :: Assignment ) ,
1710+ _ => Ok ( Some ( Token :: Colon ) ) ,
1711+ }
16711712 }
16721713 }
16731714 ';' => self . consume_and_return ( chars, Token :: SemiColon ) ,
@@ -1758,6 +1799,41 @@ impl<'a> Tokenizer<'a> {
17581799 Some ( sch) if self . dialect . is_identifier_start ( '#' ) => {
17591800 self . tokenize_identifier_or_keyword ( [ ch, * sch] , chars)
17601801 }
1802+ Some ( & c)
1803+ if self . dialect . supports_sqlite_variable_syntax ( )
1804+ && ( c. is_ascii_alphabetic ( )
1805+ || c == '_'
1806+ || c == '$'
1807+ || c >= '\u{0080}' ) =>
1808+ {
1809+ let body = self . tokenize_sqlite_variable_body ( chars) ?;
1810+ Ok ( Some ( Token :: Placeholder ( format ! ( "#{body}" ) ) ) )
1811+ }
1812+ Some ( ':' ) if self . dialect . supports_sqlite_variable_syntax ( ) => {
1813+ let mut lookahead = chars. peekable . clone ( ) ;
1814+ lookahead. next ( ) ; // skip first ':'
1815+ if lookahead. peek ( ) == Some ( & ':' ) {
1816+ chars. next ( ) ; // consume first ':'
1817+ chars. next ( ) ; // consume second ':'
1818+ match chars. peek ( ) . copied ( ) {
1819+ Some ( c)
1820+ if c. is_ascii_alphanumeric ( )
1821+ || c == '_'
1822+ || c == '$'
1823+ || c >= '\u{0080}' =>
1824+ {
1825+ let body = self . tokenize_sqlite_variable_body ( chars) ?;
1826+ Ok ( Some ( Token :: Placeholder ( format ! ( "#::{body}" ) ) ) )
1827+ }
1828+ _ => self . tokenizer_error (
1829+ chars. location ( ) ,
1830+ "expected identifier after '#::'" ,
1831+ ) ,
1832+ }
1833+ } else {
1834+ self . start_binop ( chars, "#" , Token :: Sharp )
1835+ }
1836+ }
17611837 _ => self . start_binop ( chars, "#" , Token :: Sharp ) ,
17621838 }
17631839 }
@@ -1802,6 +1878,41 @@ impl<'a> Tokenizer<'a> {
18021878 Some ( sch) if self . dialect . is_identifier_start ( '@' ) => {
18031879 self . tokenize_identifier_or_keyword ( [ ch, * sch] , chars)
18041880 }
1881+ Some ( & c)
1882+ if self . dialect . supports_sqlite_variable_syntax ( )
1883+ && ( c. is_ascii_alphanumeric ( )
1884+ || c == '_'
1885+ || c == '$'
1886+ || c >= '\u{0080}' ) =>
1887+ {
1888+ let body = self . tokenize_sqlite_variable_body ( chars) ?;
1889+ Ok ( Some ( Token :: Placeholder ( format ! ( "@{body}" ) ) ) )
1890+ }
1891+ Some ( ':' ) if self . dialect . supports_sqlite_variable_syntax ( ) => {
1892+ let mut lookahead = chars. peekable . clone ( ) ;
1893+ lookahead. next ( ) ; // skip first ':'
1894+ if lookahead. peek ( ) == Some ( & ':' ) {
1895+ chars. next ( ) ; // consume first ':'
1896+ chars. next ( ) ; // consume second ':'
1897+ match chars. peek ( ) . copied ( ) {
1898+ Some ( c)
1899+ if c. is_ascii_alphanumeric ( )
1900+ || c == '_'
1901+ || c == '$'
1902+ || c >= '\u{0080}' =>
1903+ {
1904+ let body = self . tokenize_sqlite_variable_body ( chars) ?;
1905+ Ok ( Some ( Token :: Placeholder ( format ! ( "@::{body}" ) ) ) )
1906+ }
1907+ _ => self . tokenizer_error (
1908+ chars. location ( ) ,
1909+ "expected identifier after '@::'" ,
1910+ ) ,
1911+ }
1912+ } else {
1913+ Ok ( Some ( Token :: AtSign ) )
1914+ }
1915+ }
18051916 _ => Ok ( Some ( Token :: AtSign ) ) ,
18061917 }
18071918 }
@@ -1902,6 +2013,49 @@ impl<'a> Tokenizer<'a> {
19022013 }
19032014 }
19042015
2016+ /// Reads the body of a SQLite parameter variable after its prefix char.
2017+ /// Body chars are alphanumeric, `_`, `$`, and any char above U+007F.
2018+ /// Handles TCL `::` namespace separators and `(subscript)` forms.
2019+ fn tokenize_sqlite_variable_body ( & self , chars : & mut State ) -> Result < String , TokenizerError > {
2020+ let mut body = peeking_take_while ( chars, |c| {
2021+ c. is_ascii_alphanumeric ( ) || c == '_' || c == '$' || c >= '\u{0080}'
2022+ } ) ;
2023+ loop {
2024+ if chars. peek ( ) != Some ( & ':' ) {
2025+ break ;
2026+ }
2027+ chars. next ( ) ; // consume first ':'
2028+ if chars. peek ( ) != Some ( & ':' ) {
2029+ // Single ':', not a TCL namespace separator
2030+ break ;
2031+ }
2032+ chars. next ( ) ; // consume second ':'
2033+ body. push_str ( "::" ) ;
2034+ body. push_str ( & peeking_take_while ( chars, |c| {
2035+ c. is_ascii_alphanumeric ( ) || c == '_' || c == '$' || c >= '\u{0080}'
2036+ } ) ) ;
2037+ }
2038+ if chars. peek ( ) == Some ( & '(' ) {
2039+ chars. next ( ) ; // consume '('
2040+ let sub = peeking_take_while ( chars, |c| c != ')' && !c. is_ascii_whitespace ( ) ) ;
2041+ match chars. peek ( ) {
2042+ Some ( & ')' ) => {
2043+ chars. next ( ) ; // consume ')'
2044+ body. push ( '(' ) ;
2045+ body. push_str ( & sub) ;
2046+ body. push ( ')' ) ;
2047+ }
2048+ _ => {
2049+ return self . tokenizer_error (
2050+ chars. location ( ) ,
2051+ "unterminated SQLite variable subscript" ,
2052+ ) ;
2053+ }
2054+ }
2055+ }
2056+ Ok ( body)
2057+ }
2058+
19052059 /// Tokenize dollar preceded value (i.e: a string/placeholder)
19062060 fn tokenize_dollar_preceded_value ( & self , chars : & mut State ) -> Result < Token , TokenizerError > {
19072061 let mut s = String :: new ( ) ;
@@ -1998,6 +2152,38 @@ impl<'a> Tokenizer<'a> {
19982152 }
19992153 }
20002154 } else {
2155+ if self . dialect . supports_sqlite_variable_syntax ( ) {
2156+ loop {
2157+ if chars. peek ( ) != Some ( & ':' ) {
2158+ break ;
2159+ }
2160+ chars. next ( ) ; // consume first ':'
2161+ if chars. peek ( ) != Some ( & ':' ) {
2162+ break ;
2163+ }
2164+ chars. next ( ) ; // consume second ':'
2165+ value. push_str ( "::" ) ;
2166+ value. push_str ( & peeking_take_while ( chars, |ch| {
2167+ ch. is_ascii_alphanumeric ( ) || ch == '_' || ch == '$' || ch >= '\u{0080}'
2168+ } ) ) ;
2169+ }
2170+ if !value. is_empty ( ) && chars. peek ( ) == Some ( & '(' ) {
2171+ chars. next ( ) ; // consume '('
2172+ let sub =
2173+ peeking_take_while ( chars, |c| c != ')' && !c. is_ascii_whitespace ( ) ) ;
2174+ if chars. peek ( ) == Some ( & ')' ) {
2175+ chars. next ( ) ; // consume ')'
2176+ value. push ( '(' ) ;
2177+ value. push_str ( & sub) ;
2178+ value. push ( ')' ) ;
2179+ } else {
2180+ return self . tokenizer_error (
2181+ chars. location ( ) ,
2182+ "unterminated SQLite variable subscript" ,
2183+ ) ;
2184+ }
2185+ }
2186+ }
20012187 return Ok ( Token :: Placeholder ( format ! ( "${value}" ) ) ) ;
20022188 }
20032189 }
0 commit comments