diff --git a/src/ast/mod.rs b/src/ast/mod.rs index d3a028b0b..159193b64 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -898,6 +898,8 @@ pub enum Expr { /// IntroducedString { introducer: String, + /// The value of the constant. + /// Hint: you can unwrap the string value using `value.into_string()`. value: Value, }, /// A constant of form ` 'value'`. @@ -905,7 +907,9 @@ pub enum Expr { /// as well as constants of other types (a non-standard PostgreSQL extension). TypedString { data_type: DataType, - value: String, + /// The value of the constant. + /// Hint: you can unwrap the string value using `value.into_string()`. + value: Value, }, /// Scalar function call e.g. `LEFT(foo, 5)` Function(Function), @@ -1620,7 +1624,7 @@ impl fmt::Display for Expr { Expr::IntroducedString { introducer, value } => write!(f, "{introducer} {value}"), Expr::TypedString { data_type, value } => { write!(f, "{data_type}")?; - write!(f, " '{}'", &value::escape_single_quote_string(value)) + write!(f, " {value}") } Expr::Function(fun) => write!(f, "{fun}"), Expr::Method(method) => write!(f, "{method}"), diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 58fa27aa8..725bb130c 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -1266,7 +1266,7 @@ impl Spanned for AssignmentTarget { /// f.e. `IS NULL ` reports as `::span`. /// /// Missing spans: -/// - [Expr::TypedString] +/// - [Expr::TypedString] # missing span for data_type /// - [Expr::MatchAgainst] # MySQL specific /// - [Expr::RLike] # MySQL specific /// - [Expr::Struct] # BigQuery specific @@ -1362,7 +1362,7 @@ impl Spanned for Expr { .union(&union_spans(collation.0.iter().map(|i| i.span()))), Expr::Nested(expr) => expr.span(), Expr::Value(value) => value.span(), - Expr::TypedString { .. } => Span::empty(), + Expr::TypedString { value, .. } => value.span(), Expr::Function(function) => function.span(), Expr::GroupingSets(vec) => { union_spans(vec.iter().flat_map(|i| i.iter().map(|k| k.span()))) diff --git a/src/ast/value.rs b/src/ast/value.rs index 1b16646be..5798b5404 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -97,6 +97,32 @@ pub enum Value { Placeholder(String), } +impl Value { + /// If the underlying literal is a string, regardless of quote style, returns the associated string value + pub fn into_string(self) -> Option { + match self { + Value::SingleQuotedString(s) + | Value::DoubleQuotedString(s) + | Value::TripleSingleQuotedString(s) + | Value::TripleDoubleQuotedString(s) + | Value::SingleQuotedByteStringLiteral(s) + | Value::DoubleQuotedByteStringLiteral(s) + | Value::TripleSingleQuotedByteStringLiteral(s) + | Value::TripleDoubleQuotedByteStringLiteral(s) + | Value::SingleQuotedRawStringLiteral(s) + | Value::DoubleQuotedRawStringLiteral(s) + | Value::TripleSingleQuotedRawStringLiteral(s) + | Value::TripleDoubleQuotedRawStringLiteral(s) + | Value::EscapedStringLiteral(s) + | Value::UnicodeStringLiteral(s) + | Value::NationalStringLiteral(s) + | Value::HexStringLiteral(s) => Some(s), + Value::DollarQuotedString(s) => Some(s.value), + _ => None, + } + } +} + impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 179c120bb..b3eb8c791 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1316,7 +1316,7 @@ impl<'a> Parser<'a> { DataType::Custom(..) => parser_err!("dummy", loc), data_type => Ok(Expr::TypedString { data_type, - value: parser.parse_literal_string()?, + value: parser.parse_value()?, }), } })?; diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 921a37a8a..af4083c48 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -39,43 +39,45 @@ fn parse_literal_string() { r#"'''triple-single'unescaped''', "#, r#""double\"escaped", "#, r#""""triple-double\"escaped""", "#, - r#""""triple-double"unescaped""""#, + r#""""triple-double"unescaped""", "#, + r#""""triple-double'unescaped""", "#, + r#"'''triple-single"unescaped'''"#, ); let dialect = TestedDialects::new_with_options( vec![Box::new(BigQueryDialect {})], ParserOptions::new().with_unescape(false), ); let select = dialect.verified_only_select(sql); - assert_eq!(10, select.projection.len()); + assert_eq!(12, select.projection.len()); assert_eq!( - &Expr::Value(Value::SingleQuotedString("single".to_string())), + &Expr::Value(Value::SingleQuotedString("single".into())), expr_from_projection(&select.projection[0]) ); assert_eq!( - &Expr::Value(Value::DoubleQuotedString("double".to_string())), + &Expr::Value(Value::DoubleQuotedString("double".into())), expr_from_projection(&select.projection[1]) ); assert_eq!( - &Expr::Value(Value::TripleSingleQuotedString("triple-single".to_string())), + &Expr::Value(Value::TripleSingleQuotedString("triple-single".into())), expr_from_projection(&select.projection[2]) ); assert_eq!( - &Expr::Value(Value::TripleDoubleQuotedString("triple-double".to_string())), + &Expr::Value(Value::TripleDoubleQuotedString("triple-double".into())), expr_from_projection(&select.projection[3]) ); assert_eq!( - &Expr::Value(Value::SingleQuotedString(r#"single\'escaped"#.to_string())), + &Expr::Value(Value::SingleQuotedString(r#"single\'escaped"#.into())), expr_from_projection(&select.projection[4]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedString( - r#"triple-single\'escaped"#.to_string() + r#"triple-single\'escaped"#.into() )), expr_from_projection(&select.projection[5]) ); assert_eq!( &Expr::Value(Value::TripleSingleQuotedString( - r#"triple-single'unescaped"#.to_string() + r#"triple-single'unescaped"#.into() )), expr_from_projection(&select.projection[6]) ); @@ -95,6 +97,18 @@ fn parse_literal_string() { )), expr_from_projection(&select.projection[9]) ); + assert_eq!( + &Expr::Value(Value::TripleDoubleQuotedString( + r#"triple-double'unescaped"#.to_string() + )), + expr_from_projection(&select.projection[10]) + ); + assert_eq!( + &Expr::Value(Value::TripleSingleQuotedString( + r#"triple-single"unescaped"#.to_string() + )), + expr_from_projection(&select.projection[11]) + ); } #[test] @@ -579,7 +593,7 @@ fn parse_tuple_struct_literal() { &Expr::Tuple(vec![ Expr::Value(number("1")), Expr::Value(number("1.0")), - Expr::Value(Value::SingleQuotedString("123".to_string())), + Expr::Value(Value::SingleQuotedString("123".into())), Expr::Value(Value::Boolean(true)) ]), expr_from_projection(&select.projection[1]) @@ -607,7 +621,7 @@ fn parse_typeless_struct_syntax() { assert_eq!( &Expr::Struct { - values: vec![Expr::Value(Value::SingleQuotedString("abc".to_string())),], + values: vec![Expr::Value(Value::SingleQuotedString("abc".into())),], fields: Default::default() }, expr_from_projection(&select.projection[1]) @@ -630,7 +644,7 @@ fn parse_typeless_struct_syntax() { name: Ident::from("a") }, Expr::Named { - expr: Expr::Value(Value::SingleQuotedString("abc".to_string())).into(), + expr: Expr::Value(Value::SingleQuotedString("abc".into())).into(), name: Ident::from("b") }, ], @@ -795,9 +809,7 @@ fn parse_typed_struct_syntax_bigquery() { assert_eq!(4, select.projection.len()); assert_eq!( &Expr::Struct { - values: vec![Expr::Value(Value::DoubleQuotedString( - "2011-05-05".to_string() - )),], + values: vec![Expr::Value(Value::DoubleQuotedString("2011-05-05".into())),], fields: vec![StructField { field_name: None, field_type: DataType::Date @@ -809,7 +821,7 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Datetime(None), - value: "1999-01-01 01:23:34.45".to_string() + value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()) },], fields: vec![StructField { field_name: None, @@ -845,7 +857,7 @@ fn parse_typed_struct_syntax_bigquery() { assert_eq!( &Expr::Struct { values: vec![Expr::Interval(Interval { - value: Box::new(Expr::Value(Value::SingleQuotedString("2".to_string()))), + value: Box::new(Expr::Value(Value::SingleQuotedString("2".into()))), leading_field: Some(DateTimeField::Hour), leading_precision: None, last_field: None, @@ -862,7 +874,9 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::JSON, - value: r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.to_string() + value: Value::SingleQuotedString( + r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() + ) },], fields: vec![StructField { field_name: None, @@ -877,7 +891,7 @@ fn parse_typed_struct_syntax_bigquery() { assert_eq!(3, select.projection.len()); assert_eq!( &Expr::Struct { - values: vec![Expr::Value(Value::DoubleQuotedString("foo".to_string())),], + values: vec![Expr::Value(Value::DoubleQuotedString("foo".into())),], fields: vec![StructField { field_name: None, field_type: DataType::String(Some(42)) @@ -889,7 +903,7 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: "2008-12-25 15:30:00 America/Los_Angeles".to_string() + value: Value::SingleQuotedString("2008-12-25 15:30:00 America/Los_Angeles".into()) },], fields: vec![StructField { field_name: None, @@ -903,7 +917,7 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Time(None, TimezoneInfo::None), - value: "15:30:00".to_string() + value: Value::SingleQuotedString("15:30:00".into()) },], fields: vec![StructField { field_name: None, @@ -920,7 +934,7 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Numeric(ExactNumberInfo::None), - value: "1".to_string() + value: Value::SingleQuotedString("1".into()) },], fields: vec![StructField { field_name: None, @@ -933,7 +947,7 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: "1".to_string() + value: Value::SingleQuotedString("1".into()) },], fields: vec![StructField { field_name: None, @@ -1110,9 +1124,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { assert_eq!(4, select.projection.len()); assert_eq!( &Expr::Struct { - values: vec![Expr::Value(Value::SingleQuotedString( - "2011-05-05".to_string() - )),], + values: vec![Expr::Value(Value::SingleQuotedString("2011-05-05".into())),], fields: vec![StructField { field_name: None, field_type: DataType::Date @@ -1124,7 +1136,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Datetime(None), - value: "1999-01-01 01:23:34.45".to_string() + value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()) },], fields: vec![StructField { field_name: None, @@ -1160,7 +1172,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { assert_eq!( &Expr::Struct { values: vec![Expr::Interval(Interval { - value: Box::new(Expr::Value(Value::SingleQuotedString("1".to_string()))), + value: Box::new(Expr::Value(Value::SingleQuotedString("1".into()))), leading_field: Some(DateTimeField::Month), leading_precision: None, last_field: None, @@ -1177,7 +1189,9 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::JSON, - value: r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.to_string() + value: Value::SingleQuotedString( + r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() + ) },], fields: vec![StructField { field_name: None, @@ -1192,7 +1206,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { assert_eq!(3, select.projection.len()); assert_eq!( &Expr::Struct { - values: vec![Expr::Value(Value::SingleQuotedString("foo".to_string())),], + values: vec![Expr::Value(Value::SingleQuotedString("foo".into())),], fields: vec![StructField { field_name: None, field_type: DataType::String(Some(42)) @@ -1204,7 +1218,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: "2008-12-25 15:30:00 America/Los_Angeles".to_string() + value: Value::SingleQuotedString("2008-12-25 15:30:00 America/Los_Angeles".into()) },], fields: vec![StructField { field_name: None, @@ -1218,7 +1232,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Time(None, TimezoneInfo::None), - value: "15:30:00".to_string() + value: Value::SingleQuotedString("15:30:00".into()) },], fields: vec![StructField { field_name: None, @@ -1235,7 +1249,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Numeric(ExactNumberInfo::None), - value: "1".to_string() + value: Value::SingleQuotedString("1".into()) },], fields: vec![StructField { field_name: None, @@ -1248,7 +1262,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: "1".to_string() + value: Value::SingleQuotedString("1".into()) },], fields: vec![StructField { field_name: None, @@ -1276,7 +1290,7 @@ fn parse_typed_struct_with_field_name_bigquery() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::Value(Value::DoubleQuotedString("foo".to_string())),], + values: vec![Expr::Value(Value::DoubleQuotedString("foo".into())),], fields: vec![StructField { field_name: Some(Ident::from("y")), field_type: DataType::String(None) @@ -1323,7 +1337,7 @@ fn parse_typed_struct_with_field_name_bigquery_and_generic() { ); assert_eq!( &Expr::Struct { - values: vec![Expr::Value(Value::SingleQuotedString("foo".to_string())),], + values: vec![Expr::Value(Value::SingleQuotedString("foo".into())),], fields: vec![StructField { field_name: Some(Ident::from("y")), field_type: DataType::String(None) @@ -2225,6 +2239,20 @@ fn test_select_as_value() { assert_eq!(Some(ValueTableMode::AsValue), select.value_table_mode); } +#[test] +fn test_triple_quote_typed_strings() { + bigquery().verified_expr(r#"JSON '''{"foo":"bar's"}'''"#); + + let expr = bigquery().verified_expr(r#"JSON """{"foo":"bar's"}""""#); + assert_eq!( + Expr::TypedString { + data_type: DataType::JSON, + value: Value::TripleDoubleQuotedString(r#"{"foo":"bar's"}"#.into()) + }, + expr + ); +} + #[test] fn test_array_agg() { bigquery_and_generic().verified_expr("ARRAY_AGG(state)"); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 5dd74e1fa..9f1deb849 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -5386,7 +5386,7 @@ fn parse_literal_date() { assert_eq!( &Expr::TypedString { data_type: DataType::Date, - value: "1999-01-01".into(), + value: Value::SingleQuotedString("1999-01-01".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5399,7 +5399,7 @@ fn parse_literal_time() { assert_eq!( &Expr::TypedString { data_type: DataType::Time(None, TimezoneInfo::None), - value: "01:23:34".into(), + value: Value::SingleQuotedString("01:23:34".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5412,7 +5412,7 @@ fn parse_literal_datetime() { assert_eq!( &Expr::TypedString { data_type: DataType::Datetime(None), - value: "1999-01-01 01:23:34.45".into(), + value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5425,7 +5425,7 @@ fn parse_literal_timestamp_without_time_zone() { assert_eq!( &Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: "1999-01-01 01:23:34".into(), + value: Value::SingleQuotedString("1999-01-01 01:23:34".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5440,7 +5440,7 @@ fn parse_literal_timestamp_with_time_zone() { assert_eq!( &Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::Tz), - value: "1999-01-01 01:23:34Z".into(), + value: Value::SingleQuotedString("1999-01-01 01:23:34Z".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5992,7 +5992,8 @@ fn parse_json_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::JSON, - value: r#"{ + value: Value::SingleQuotedString( + r#"{ "id": 10, "type": "fruit", "name": "apple", @@ -6012,12 +6013,30 @@ fn parse_json_keyword() { ] } }"# - .into() + .to_string() + ) }, expr_from_projection(only(&select.projection)), ); } +#[test] +fn parse_typed_strings() { + let expr = verified_expr(r#"JSON '{"foo":"bar"}'"#); + assert_eq!( + Expr::TypedString { + data_type: DataType::JSON, + value: Value::SingleQuotedString(r#"{"foo":"bar"}"#.into()) + }, + expr + ); + + if let Expr::TypedString { data_type, value } = expr { + assert_eq!(DataType::JSON, data_type); + assert_eq!(r#"{"foo":"bar"}"#, value.into_string().unwrap()); + } +} + #[test] fn parse_bignumeric_keyword() { let sql = r#"SELECT BIGNUMERIC '0'"#; @@ -6025,7 +6044,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: r#"0"#.into() + value: Value::SingleQuotedString(r#"0"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6036,7 +6055,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: r#"123456"#.into() + value: Value::SingleQuotedString(r#"123456"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6047,7 +6066,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: r#"-3.14"#.into() + value: Value::SingleQuotedString(r#"-3.14"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6058,7 +6077,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: r#"-0.54321"#.into() + value: Value::SingleQuotedString(r#"-0.54321"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6069,7 +6088,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: r#"1.23456e05"#.into() + value: Value::SingleQuotedString(r#"1.23456e05"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6080,7 +6099,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: r#"-9.876e-3"#.into() + value: Value::SingleQuotedString(r#"-9.876e-3"#.into()) }, expr_from_projection(only(&select.projection)), ); diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index b3eb4f10d..c60deaf40 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4637,7 +4637,7 @@ fn parse_at_time_zone() { left: Box::new(Expr::AtTimeZone { timestamp: Box::new(Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: "2001-09-28 01:00".to_owned(), + value: Value::SingleQuotedString("2001-09-28 01:00".to_string()), }), time_zone: Box::new(Expr::Cast { kind: CastKind::DoubleColon,