Skip to content

Commit b12fb34

Browse files
authored
Merge pull request #95 from benesch/hex-literals
Support hexadecimal string literals
2 parents b8ba188 + eba3983 commit b12fb34

File tree

4 files changed

+36
-3
lines changed

4 files changed

+36
-3
lines changed

src/sqlast/value.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ pub enum Value {
1111
SingleQuotedString(String),
1212
/// N'string value'
1313
NationalStringLiteral(String),
14+
/// X'hex value'
15+
HexStringLiteral(String),
1416
/// Boolean value true or false
1517
Boolean(bool),
1618
/// NULL value in insert statements,
@@ -24,6 +26,7 @@ impl ToString for Value {
2426
Value::Double(v) => v.to_string(),
2527
Value::SingleQuotedString(v) => format!("'{}'", escape_single_quote_string(v)),
2628
Value::NationalStringLiteral(v) => format!("N'{}'", v),
29+
Value::HexStringLiteral(v) => format!("X'{}'", v),
2730
Value::Boolean(v) => v.to_string(),
2831
Value::Null => "NULL".to_string(),
2932
}

src/sqlparser.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,10 @@ impl Parser {
239239
expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?),
240240
})
241241
}
242-
Token::Number(_) | Token::SingleQuotedString(_) | Token::NationalStringLiteral(_) => {
242+
Token::Number(_)
243+
| Token::SingleQuotedString(_)
244+
| Token::NationalStringLiteral(_)
245+
| Token::HexStringLiteral(_) => {
243246
self.prev_token();
244247
self.parse_sql_value()
245248
}
@@ -1042,6 +1045,7 @@ impl Parser {
10421045
Token::NationalStringLiteral(ref s) => {
10431046
Ok(Value::NationalStringLiteral(s.to_string()))
10441047
}
1048+
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
10451049
_ => parser_err!(format!("Unsupported value: {:?}", t)),
10461050
},
10471051
None => parser_err!("Expecting a value, but found EOF"),

src/sqltokenizer.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ pub enum Token {
3737
SingleQuotedString(String),
3838
/// "National" string literal: i.e: N'string'
3939
NationalStringLiteral(String),
40+
/// Hexadecimal string literal: i.e.: X'deadbeef'
41+
HexStringLiteral(String),
4042
/// Comma
4143
Comma,
4244
/// Whitespace (space, tab, etc)
@@ -97,6 +99,7 @@ impl ToString for Token {
9799
Token::Char(ref c) => c.to_string(),
98100
Token::SingleQuotedString(ref s) => format!("'{}'", s),
99101
Token::NationalStringLiteral(ref s) => format!("N'{}'", s),
102+
Token::HexStringLiteral(ref s) => format!("X'{}'", s),
100103
Token::Comma => ",".to_string(),
101104
Token::Whitespace(ws) => ws.to_string(),
102105
Token::Eq => "=".to_string(),
@@ -286,6 +289,23 @@ impl<'a> Tokenizer<'a> {
286289
}
287290
}
288291
}
292+
// The spec only allows an uppercase 'X' to introduce a hex
293+
// string, but PostgreSQL, at least, allows a lowercase 'x' too.
294+
x @ 'x' | x @ 'X' => {
295+
chars.next(); // consume, to check the next char
296+
match chars.peek() {
297+
Some('\'') => {
298+
// X'...' - a <binary string literal>
299+
let s = self.tokenize_single_quoted_string(chars);
300+
Ok(Some(Token::HexStringLiteral(s)))
301+
}
302+
_ => {
303+
// regular identifier starting with an "X"
304+
let s = self.tokenize_word(x, chars);
305+
Ok(Some(Token::make_word(&s, None)))
306+
}
307+
}
308+
}
289309
// identifier or keyword
290310
ch if self.dialect.is_identifier_start(ch) => {
291311
chars.next(); // consume the first char

tests/sqlparser_common.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -952,9 +952,9 @@ fn parse_aggregate_with_group_by() {
952952

953953
#[test]
954954
fn parse_literal_string() {
955-
let sql = "SELECT 'one', N'national string'";
955+
let sql = "SELECT 'one', N'national string', X'deadBEEF'";
956956
let select = verified_only_select(sql);
957-
assert_eq!(2, select.projection.len());
957+
assert_eq!(3, select.projection.len());
958958
assert_eq!(
959959
&ASTNode::SQLValue(Value::SingleQuotedString("one".to_string())),
960960
expr_from_projection(&select.projection[0])
@@ -963,6 +963,12 @@ fn parse_literal_string() {
963963
&ASTNode::SQLValue(Value::NationalStringLiteral("national string".to_string())),
964964
expr_from_projection(&select.projection[1])
965965
);
966+
assert_eq!(
967+
&ASTNode::SQLValue(Value::HexStringLiteral("deadBEEF".to_string())),
968+
expr_from_projection(&select.projection[2])
969+
);
970+
971+
one_statement_parses_to("SELECT x'deadBEEF'", "SELECT X'deadBEEF'");
966972
}
967973

968974
#[test]

0 commit comments

Comments
 (0)