Skip to content
4 changes: 4 additions & 0 deletions src/ast/data_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,8 @@ impl fmt::Display for DataType {
ArrayElemTypeDef::SquareBracket(t, Some(size)) => write!(f, "{t}[{size}]"),
ArrayElemTypeDef::AngleBracket(t) => write!(f, "ARRAY<{t}>"),
ArrayElemTypeDef::Parenthesis(t) => write!(f, "Array({t})"),
ArrayElemTypeDef::Qualified(t, None) => write!(f, "{t} ARRAY"),
ArrayElemTypeDef::Qualified(t, Some(size)) => write!(f, "{t} ARRAY[{size}]"),
},
DataType::Custom(ty, modifiers) => {
if modifiers.is_empty() {
Expand Down Expand Up @@ -1163,6 +1165,8 @@ pub enum ArrayElemTypeDef {
SquareBracket(Box<DataType>, Option<u64>),
/// Parenthesis style, e.g. `Array(Int64)`.
Parenthesis(Box<DataType>),
/// Qualified by a data type and optional size, e.g. `INT ARRAY` or `INT ARRAY[4]`.
Qualified(Box<DataType>, Option<u64>),
}

/// Represents different types of geometric shapes which are commonly used in
Expand Down
10 changes: 0 additions & 10 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1110,12 +1110,6 @@ pub enum Expr {
expr: Box<Expr>,
/// Target data type.
data_type: DataType,
/// [MySQL] allows CAST(... AS type ARRAY) in functional index definitions for InnoDB
/// multi-valued indices. It's not really a datatype, and is only allowed in `CAST` in key
/// specifications, so it's a flag here.
///
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html#function_cast
array: bool,
/// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by [BigQuery]
///
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
Expand Down Expand Up @@ -1976,14 +1970,10 @@ impl fmt::Display for Expr {
kind,
expr,
data_type,
array,
format,
} => match kind {
CastKind::Cast => {
write!(f, "CAST({expr} AS {data_type}")?;
if *array {
write!(f, " ARRAY")?;
}
if let Some(format) = format {
write!(f, " FORMAT {format}")?;
}
Expand Down
1 change: 0 additions & 1 deletion src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,6 @@ impl Spanned for Expr {
kind: _,
expr,
data_type: _,
array: _,
format: _,
} => expr.span(),
Expr::AtTimeZone {
Expand Down
19 changes: 14 additions & 5 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1831,7 +1831,6 @@ impl<'a> Parser<'a> {
kind: CastKind::Cast,
expr: Box::new(parser.parse_expr()?),
data_type: DataType::Binary(None),
array: false,
format: None,
})
}
Expand Down Expand Up @@ -2905,14 +2904,12 @@ impl<'a> Parser<'a> {
let expr = self.parse_expr()?;
self.expect_keyword_is(Keyword::AS)?;
let data_type = self.parse_data_type()?;
let array = self.parse_keyword(Keyword::ARRAY);
let format = self.parse_optional_cast_format()?;
self.expect_token(&Token::RParen)?;
Ok(Expr::Cast {
kind,
expr: Box::new(expr),
data_type,
array,
format,
})
}
Expand Down Expand Up @@ -4186,7 +4183,6 @@ impl<'a> Parser<'a> {
kind: CastKind::DoubleColon,
expr: Box::new(expr),
data_type: self.parse_data_type()?,
array: false,
format: None,
})
} else if Token::ExclamationMark == *tok && self.dialect.supports_factorial_operator() {
Expand Down Expand Up @@ -4443,7 +4439,6 @@ impl<'a> Parser<'a> {
kind: CastKind::DoubleColon,
expr: Box::new(expr),
data_type: self.parse_data_type()?,
array: false,
format: None,
})
}
Expand Down Expand Up @@ -13150,6 +13145,20 @@ impl<'a> Parser<'a> {
data = DataType::Array(ArrayElemTypeDef::SquareBracket(Box::new(data), size))
}
}

// Type-qualified array, e.g. `INT ARRAY` or `INT ARRAY[3]`. One-dimensional
// with a single optional size. The `ARRAY` keyword is unambiguous everywhere.
if self.parse_keyword(Keyword::ARRAY) {
let size = if self.consume_token(&Token::LBracket) {
let size = self.maybe_parse(|p| p.parse_literal_uint())?;
self.expect_token(&Token::RBracket)?;
size
} else {
None
};
data = DataType::Array(ArrayElemTypeDef::Qualified(Box::new(data), size));
}

Ok((data, trailing_bracket))
}

Expand Down
19 changes: 0 additions & 19 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3172,7 +3172,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::BigInt(None),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3185,7 +3184,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::TinyInt(None),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand Down Expand Up @@ -3217,7 +3215,6 @@ fn parse_cast() {
length: 50,
unit: None,
})),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3230,7 +3227,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Clob(None),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3243,7 +3239,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Clob(Some(50)),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3256,7 +3251,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Binary(Some(50)),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3269,7 +3263,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Varbinary(Some(BinaryLength::IntegerLength { length: 50 })),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3282,7 +3275,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Blob(None),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3295,7 +3287,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::Blob(Some(50)),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3308,7 +3299,6 @@ fn parse_cast() {
kind: CastKind::Cast,
expr: Box::new(Expr::Identifier(Ident::new("details"))),
data_type: DataType::JSONB,
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand All @@ -3324,7 +3314,6 @@ fn parse_try_cast() {
kind: CastKind::TryCast,
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::BigInt(None),
array: false,
format: None,
},
expr_from_projection(only(&select.projection))
Expand Down Expand Up @@ -6698,7 +6687,6 @@ fn interval_disallow_interval_expr_double_colon() {
fractional_seconds_precision: None,
})),
data_type: DataType::Text,
array: false,
format: None,
}
)
Expand All @@ -6716,7 +6704,6 @@ fn parse_text_type_modifier_double_colon_cast() {
ObjectName::from(vec![Ident::new("TEXT")]),
vec!["16777216".to_string()]
),
array: false,
format: None,
}
);
Expand Down Expand Up @@ -9482,7 +9469,6 @@ fn parse_double_colon_cast_at_timezone() {
.with_empty_span()
)),
data_type: DataType::Timestamp(None, TimezoneInfo::None),
array: false,
format: None
}),
time_zone: Box::new(Expr::Value(
Expand Down Expand Up @@ -13897,7 +13883,6 @@ fn test_dictionary_syntax() {
(Value::SingleQuotedString("2023-04-01".to_owned())).with_empty_span(),
)),
data_type: DataType::Timestamp(None, TimezoneInfo::None),
array: false,
format: None,
}),
},
Expand All @@ -13909,7 +13894,6 @@ fn test_dictionary_syntax() {
(Value::SingleQuotedString("2023-04-05".to_owned())).with_empty_span(),
)),
data_type: DataType::Timestamp(None, TimezoneInfo::None),
array: false,
format: None,
}),
},
Expand Down Expand Up @@ -14206,7 +14190,6 @@ fn test_extract_seconds_ok() {
fields: None,
precision: None
},
array: false,
format: None,
}),
}
Expand Down Expand Up @@ -14237,7 +14220,6 @@ fn test_extract_seconds_ok() {
fields: None,
precision: None,
},
array: false,
format: None,
}),
})],
Expand Down Expand Up @@ -14295,7 +14277,6 @@ fn test_extract_seconds_single_quote_ok() {
fields: None,
precision: None
},
array: false,
format: None,
}),
}
Expand Down
1 change: 0 additions & 1 deletion tests/sqlparser_databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ fn data_type_timestamp_ntz() {
"created_at".into()
)))),
data_type: DataType::TimestampNtz(None),
array: false,
format: None
}
);
Expand Down
1 change: 0 additions & 1 deletion tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,6 @@ fn test_duckdb_specific_int_types() {
Value::Number("123".parse().unwrap(), false).with_empty_span()
)),
data_type: data_type.clone(),
array: false,
format: None,
},
expr_from_projection(&select.projection[0])
Expand Down
44 changes: 40 additions & 4 deletions tests/sqlparser_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,7 +886,6 @@ fn test_functional_key_part() {
)),
}),
data_type: DataType::Unsigned,
array: false,
format: None,
})),
);
Expand All @@ -903,8 +902,10 @@ fn test_functional_key_part() {
Value::SingleQuotedString("$.fields".to_string()).with_empty_span()
)),
}),
data_type: DataType::Unsigned,
array: true,
data_type: DataType::Array(ArrayElemTypeDef::Qualified(
Box::new(DataType::Unsigned),
None,
)),
format: None,
})),
);
Expand Down Expand Up @@ -4278,12 +4279,47 @@ fn parse_cast_integers() {

#[test]
fn parse_cast_array() {
mysql().verified_expr("CAST(foo AS SIGNED ARRAY)");
// The element type may be any type accepted by CAST().
for ty in [
"SIGNED",
"UNSIGNED",
"CHAR",
"CHAR(10)",
"BINARY",
"BINARY(5)",
"DATE",
"TIME",
"DATETIME",
"DECIMAL",
"DECIMAL(10,2)",
"DOUBLE",
"FLOAT",
"YEAR",
] {
mysql().verified_expr(&format!("CAST(foo AS {ty} ARRAY)"));
}

// `ARRAY` on its own is not a valid CAST target type.
mysql()
.run_parser_method("CAST(foo AS ARRAY)", |p| p.parse_expr())
.expect_err("ARRAY alone is not a type");
}

#[test]
fn parse_multi_valued_index() {
// `CAST(... AS <type> ARRAY)` key part in CREATE TABLE, CREATE INDEX, and
// ALTER TABLE. See https://dev.mysql.com/doc/refman/8.0/en/create-index.html
mysql_and_generic().verified_stmt(
"CREATE TABLE customers (id BIGINT, custinfo JSON, INDEX zips ((CAST(custinfo -> '$.zipcode' AS UNSIGNED ARRAY))))",
);
mysql_and_generic().verified_stmt(
"CREATE INDEX zips ON customers((CAST(custinfo -> '$.zipcode' AS UNSIGNED ARRAY)))",
);
mysql_and_generic().verified_stmt(
"ALTER TABLE customers ADD INDEX zips ((CAST(custinfo -> '$.zipcode' AS UNSIGNED ARRAY)))",
);
}

#[test]
fn parse_match_against_with_alias() {
let sql = "SELECT tbl.ProjectID FROM surveys.tbl1 AS tbl WHERE MATCH (tbl.ReferenceID) AGAINST ('AAA' IN BOOLEAN MODE)";
Expand Down
Loading
Loading