Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/query/ast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum-as-inner = { workspace = true }
ethnum = { workspace = true }
fast-float2 = { workspace = true }
fastrace = { workspace = true }
hex = { workspace = true }
indent = { workspace = true }
itertools = { workspace = true }
logos = { workspace = true }
Expand Down
4 changes: 4 additions & 0 deletions src/query/ast/src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,7 @@ pub enum Literal {
precision: u8,
scale: u8,
},
Binary(Vec<u8>),
// Quoted string literal value
String(String),
Boolean(bool),
Expand Down Expand Up @@ -1074,6 +1075,9 @@ impl Display for Literal {
write!(f, "{s}")
}
}
Literal::Binary(val) => {
write!(f, "X'{}'", hex::encode_upper(val))
}
Literal::String(val) => {
write!(f, "{}", QuotedString(val, '\''))
}
Expand Down
44 changes: 29 additions & 15 deletions src/query/ast/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1604,11 +1604,19 @@ pub fn expr_element(i: Input) -> IResult<WithSpan<ExprElement>> {
})
},
);
let hex_uint = map_res(literal_hex_str, |str| {
let mysql_hex_uint = map_res(mysql_literal_hex_str, |str| {
Ok(ExprElement::Literal {
value: parse_uint(str, 16).map_err(nom::Err::Failure)?,
})
});
let pg_hex_binary = map_res(pg_literal_hex_str, |str| {
Ok(ExprElement::Literal {
value: Literal::Binary(
hex::decode(str)
.map_err(|_| nom::Err::Failure(ErrorKind::Other("invalid hex literal")))?,
),
})
});
let decimal_float = map_res(
verify(
rule! {
Expand Down Expand Up @@ -1757,7 +1765,8 @@ pub fn expr_element(i: Input) -> IResult<WithSpan<ExprElement>> {
LiteralCodeString => with_span!(code_string).parse(i),
LiteralInteger => with_span!(decimal_uint).parse(i),
LiteralFloat => with_span!(rule!{ #decimal_float | #dot_number_map_access }).parse(i),
MySQLLiteralHex | PGLiteralHex => with_span!(hex_uint).parse(i),
MySQLLiteralHex => with_span!(mysql_hex_uint).parse(i),
PGLiteralHex => with_span!(pg_hex_binary).parse(i),
TRUE | FALSE => with_span!(boolean).parse(i),
NULL => with_span!(null).parse(i),
ROW => with_span!(column_row).parse(i),
Expand Down Expand Up @@ -1921,9 +1930,14 @@ pub fn literal(i: Input) -> IResult<Literal> {
},
|token| parse_uint(token.text(), 10).map_err(nom::Err::Failure),
);
let mut hex_uint = map_res(literal_hex_str, |str| {
let mut mysql_hex_uint = map_res(mysql_literal_hex_str, |str| {
parse_uint(str, 16).map_err(nom::Err::Failure)
});
let mut pg_hex_binary = map_res(pg_literal_hex_str, |str| {
hex::decode(str)
.map(Literal::Binary)
.map_err(|_| nom::Err::Failure(ErrorKind::Other("invalid hex literal")))
});
let mut decimal_float = map_res(
rule! {
LiteralFloat
Expand All @@ -1936,7 +1950,8 @@ pub fn literal(i: Input) -> IResult<Literal> {
LiteralCodeString => code_string.parse(i),
LiteralInteger => decimal_uint.parse(i),
LiteralFloat => decimal_float.parse(i),
MySQLLiteralHex | PGLiteralHex => hex_uint(i),
MySQLLiteralHex => mysql_hex_uint.parse(i),
PGLiteralHex => pg_hex_binary.parse(i),
TRUE | FALSE => boolean.parse(i),
NULL => null.parse(i),
);
Expand All @@ -1949,25 +1964,24 @@ pub fn literal(i: Input) -> IResult<Literal> {
)))
}

pub fn literal_hex_str(i: Input<'_>) -> IResult<'_, &str> {
pub fn mysql_literal_hex_str(i: Input<'_>) -> IResult<'_, &str> {
// 0XFFFF
let mysql_hex = map(
map(
rule! {
MySQLLiteralHex
},
|token| &token.text()[2..],
);
)
.parse(i)
}

pub fn pg_literal_hex_str(i: Input<'_>) -> IResult<'_, &str> {
// x'FFFF'
let pg_hex = map(
map(
rule! {
PGLiteralHex
},
|token| &token.text()[2..token.text().len() - 1],
);

rule!(
#mysql_hex
| #pg_hex
)
.parse(i)
}
Expand All @@ -1980,7 +1994,7 @@ pub fn literal_u64(i: Input) -> IResult<u64> {
},
|token| u64::from_str_radix(token.text(), 10).map_err(|e| nom::Err::Failure(e.into())),
);
let hex = map_res(literal_hex_str, |lit| {
let hex = map_res(mysql_literal_hex_str, |lit| {
u64::from_str_radix(lit, 16).map_err(|e| nom::Err::Failure(e.into()))
});

Expand All @@ -1999,7 +2013,7 @@ pub fn literal_i64(i: Input) -> IResult<i64> {
},
|token| i64::from_str_radix(token.text(), 10).map_err(|e| nom::Err::Failure(e.into())),
);
let hex = map_res(literal_hex_str, |lit| {
let hex = map_res(mysql_literal_hex_str, |lit| {
i64::from_str_radix(lit, 16).map_err(|e| nom::Err::Failure(e.into()))
});

Expand Down
1 change: 1 addition & 0 deletions src/query/ast/tests/it/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,7 @@ fn test_expr_error() {
"#,
r#"CAST(1 AS STRING) ESCAPE '$'"#,
r#"1 + 1 ESCAPE '$'"#,
r#"x'ABC'"#,
];

for case in cases {
Expand Down
13 changes: 13 additions & 0 deletions src/query/ast/tests/it/testdata/expr-error.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,16 @@ error:
| while parsing expression


---------- Input ----------
x'ABC'
---------- Output ---------
error:
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git diff --check flags trailing whitespace on this added line. Please remove the extra space after error: so the branch stays clean.

--> SQL:1:1
|
1 | x'ABC'
| ^^^^^^
| |
| expecting `<LiteralString>`, '<LiteralCodeString>', '<LiteralInteger>', '<LiteralFloat>', 'TRUE', 'FALSE', or more ...
| while parsing expression


46 changes: 34 additions & 12 deletions src/query/ast/tests/it/testdata/expr.txt
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ FunctionCall {
---------- Input ----------
[42, 3.5, 4., .001, 5e2, 1.925e-3, .38e+7, 1.e-01, 0xfff, x'deedbeef']
---------- Output ---------
[42, 3.5, 4, 0.001, 500, 0.001925, 3800000, 0.1, 4095, 3740122863]
[42, 3.5, 4, 0.001, 500, 0.001925, 3800000, 0.1, 4095, X'DEEDBEEF']
---------- AST ------------
Array {
span: Some(
Expand Down Expand Up @@ -219,8 +219,13 @@ Array {
span: Some(
58..69,
),
value: UInt64(
3740122863,
value: Binary(
[
222,
237,
190,
239,
],
),
},
],
Expand Down Expand Up @@ -262,17 +267,31 @@ Literal {
---------- Input ----------
x'123456789012345678901234567890'
---------- Output ---------
94522879687365475552814062743484560
X'123456789012345678901234567890'
---------- AST ------------
Literal {
span: Some(
0..33,
),
value: Decimal256 {
value: 94522879687365475552814062743484560,
precision: 76,
scale: 0,
},
value: Binary(
[
18,
52,
86,
120,
144,
18,
52,
86,
120,
144,
18,
52,
86,
120,
144,
],
),
}


Expand Down Expand Up @@ -895,7 +914,7 @@ BinaryOp {
---------- Input ----------
0XFF + 0xff + 0xa + x'ffff'
---------- Output ---------
255 + 255 + 10 + 65535
255 + 255 + 10 + X'FFFF'
---------- AST ------------
BinaryOp {
span: Some(
Expand Down Expand Up @@ -942,8 +961,11 @@ BinaryOp {
span: Some(
20..27,
),
value: UInt64(
65535,
value: Binary(
[
255,
255,
],
),
},
}
Expand Down
3 changes: 2 additions & 1 deletion src/query/sql/src/planner/semantic/type_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5471,6 +5471,7 @@ impl<'a> TypeChecker<'a> {
DecimalSize::new_unchecked(*precision, *scale),
)),
Literal::Float64(float) => Scalar::Number(NumberScalar::Float64((*float).into())),
Literal::Binary(bytes) => Scalar::Binary(bytes.clone()),
Literal::String(string) => Scalar::String(string.clone()),
Literal::Boolean(boolean) => Scalar::Boolean(*boolean),
Literal::Null => Scalar::Null,
Expand Down Expand Up @@ -5506,7 +5507,7 @@ impl<'a> TypeChecker<'a> {
)),
Literal::Float64(v) => Scalar::Number(NumberScalar::Float64((-*v).into())),
Literal::Null => Scalar::Null,
Literal::String(_) | Literal::Boolean(_) => {
Literal::Binary(_) | Literal::String(_) | Literal::Boolean(_) => {
return Err(ErrorCode::InvalidArgument(format!(
"Invalid minus operator for {}",
literal
Expand Down
1 change: 1 addition & 0 deletions src/query/sql/test-support/src/expr_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ fn transform_literal(lit: ASTLiteral) -> Scalar {
i256(value),
DecimalSize::new_unchecked(precision, scale),
)),
ASTLiteral::Binary(b) => Scalar::Binary(b),
ASTLiteral::String(s) => Scalar::String(s),
ASTLiteral::Boolean(b) => Scalar::Boolean(b),
ASTLiteral::Null => Scalar::Null,
Expand Down
11 changes: 11 additions & 0 deletions tests/sqllogictests/suites/query/functions/binary_format.test
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
statement ok
drop table if exists fmt_bin

statement ok
set binary_output_format = 'hex'

query TT
select typeof(X'ABCD'), X'ABCD'
----
Binary ABCD

statement error
select X'0A' + 1

statement ok
create table fmt_bin(id int, v binary)

Expand Down
Loading