Support hexadecimal string literals

This commit is contained in:
Nikhil Benesch 2019-05-29 18:18:35 -04:00
parent 2308c1c6f7
commit eba3983268
No known key found for this signature in database
GPG key ID: F7386C5DEADABA7F
4 changed files with 36 additions and 3 deletions

View file

@ -9,6 +9,8 @@ pub enum Value {
SingleQuotedString(String), SingleQuotedString(String),
/// N'string value' /// N'string value'
NationalStringLiteral(String), NationalStringLiteral(String),
/// X'hex value'
HexStringLiteral(String),
/// Boolean value true or false /// Boolean value true or false
Boolean(bool), Boolean(bool),
/// NULL value in insert statements, /// NULL value in insert statements,
@ -22,6 +24,7 @@ impl ToString for Value {
Value::Double(v) => v.to_string(), Value::Double(v) => v.to_string(),
Value::SingleQuotedString(v) => format!("'{}'", escape_single_quote_string(v)), Value::SingleQuotedString(v) => format!("'{}'", escape_single_quote_string(v)),
Value::NationalStringLiteral(v) => format!("N'{}'", v), Value::NationalStringLiteral(v) => format!("N'{}'", v),
Value::HexStringLiteral(v) => format!("X'{}'", v),
Value::Boolean(v) => v.to_string(), Value::Boolean(v) => v.to_string(),
Value::Null => "NULL".to_string(), Value::Null => "NULL".to_string(),
} }

View file

@ -238,7 +238,10 @@ impl Parser {
expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?), expr: Box::new(self.parse_subexpr(Self::PLUS_MINUS_PREC)?),
}) })
} }
Token::Number(_) | Token::SingleQuotedString(_) | Token::NationalStringLiteral(_) => { Token::Number(_)
| Token::SingleQuotedString(_)
| Token::NationalStringLiteral(_)
| Token::HexStringLiteral(_) => {
self.prev_token(); self.prev_token();
self.parse_sql_value() self.parse_sql_value()
} }
@ -1037,6 +1040,7 @@ impl Parser {
Token::NationalStringLiteral(ref s) => { Token::NationalStringLiteral(ref s) => {
Ok(Value::NationalStringLiteral(s.to_string())) Ok(Value::NationalStringLiteral(s.to_string()))
} }
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
_ => parser_err!(format!("Unsupported value: {:?}", t)), _ => parser_err!(format!("Unsupported value: {:?}", t)),
}, },
None => parser_err!("Expecting a value, but found EOF"), None => parser_err!("Expecting a value, but found EOF"),

View file

@ -37,6 +37,8 @@ pub enum Token {
SingleQuotedString(String), SingleQuotedString(String),
/// "National" string literal: i.e: N'string' /// "National" string literal: i.e: N'string'
NationalStringLiteral(String), NationalStringLiteral(String),
/// Hexadecimal string literal: i.e.: X'deadbeef'
HexStringLiteral(String),
/// Comma /// Comma
Comma, Comma,
/// Whitespace (space, tab, etc) /// Whitespace (space, tab, etc)
@ -97,6 +99,7 @@ impl ToString for Token {
Token::Char(ref c) => c.to_string(), Token::Char(ref c) => c.to_string(),
Token::SingleQuotedString(ref s) => format!("'{}'", s), Token::SingleQuotedString(ref s) => format!("'{}'", s),
Token::NationalStringLiteral(ref s) => format!("N'{}'", s), Token::NationalStringLiteral(ref s) => format!("N'{}'", s),
Token::HexStringLiteral(ref s) => format!("X'{}'", s),
Token::Comma => ",".to_string(), Token::Comma => ",".to_string(),
Token::Whitespace(ws) => ws.to_string(), Token::Whitespace(ws) => ws.to_string(),
Token::Eq => "=".to_string(), Token::Eq => "=".to_string(),
@ -286,6 +289,23 @@ impl<'a> Tokenizer<'a> {
} }
} }
} }
// The spec only allows an uppercase 'X' to introduce a hex
// string, but PostgreSQL, at least, allows a lowercase 'x' too.
x @ 'x' | x @ 'X' => {
chars.next(); // consume, to check the next char
match chars.peek() {
Some('\'') => {
// X'...' - a <binary string literal>
let s = self.tokenize_single_quoted_string(chars);
Ok(Some(Token::HexStringLiteral(s)))
}
_ => {
// regular identifier starting with an "X"
let s = self.tokenize_word(x, chars);
Ok(Some(Token::make_word(&s, None)))
}
}
}
// identifier or keyword // identifier or keyword
ch if self.dialect.is_identifier_start(ch) => { ch if self.dialect.is_identifier_start(ch) => {
chars.next(); // consume the first char chars.next(); // consume the first char

View file

@ -923,9 +923,9 @@ fn parse_aggregate_with_group_by() {
#[test] #[test]
fn parse_literal_string() { fn parse_literal_string() {
let sql = "SELECT 'one', N'national string'"; let sql = "SELECT 'one', N'national string', X'deadBEEF'";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!(2, select.projection.len()); assert_eq!(3, select.projection.len());
assert_eq!( assert_eq!(
&ASTNode::SQLValue(Value::SingleQuotedString("one".to_string())), &ASTNode::SQLValue(Value::SingleQuotedString("one".to_string())),
expr_from_projection(&select.projection[0]) expr_from_projection(&select.projection[0])
@ -934,6 +934,12 @@ fn parse_literal_string() {
&ASTNode::SQLValue(Value::NationalStringLiteral("national string".to_string())), &ASTNode::SQLValue(Value::NationalStringLiteral("national string".to_string())),
expr_from_projection(&select.projection[1]) expr_from_projection(&select.projection[1])
); );
assert_eq!(
&ASTNode::SQLValue(Value::HexStringLiteral("deadBEEF".to_string())),
expr_from_projection(&select.projection[2])
);
one_statement_parses_to("SELECT x'deadBEEF'", "SELECT X'deadBEEF'");
} }
#[test] #[test]