Skip to content

Commit c3f6af2

Browse files
committed
Further reduce Expr to 112 bytes in size
1 parent ce91626 commit c3f6af2

File tree

11 files changed

+426
-416
lines changed

11 files changed

+426
-416
lines changed

src/ast/mod.rs

Lines changed: 126 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ impl fmt::Display for Array {
460460
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
461461
pub struct Interval {
462462
/// The interval value expression (commonly a string literal).
463-
pub value: Box<Expr>,
463+
pub value: Expr,
464464
/// Optional leading time unit (e.g., `HOUR`, `MINUTE`).
465465
pub leading_field: Option<DateTimeField>,
466466
/// Optional leading precision for the leading field.
@@ -475,7 +475,7 @@ pub struct Interval {
475475

476476
impl fmt::Display for Interval {
477477
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
478-
let value = self.value.as_ref();
478+
let value = &self.value;
479479
match (
480480
&self.leading_field,
481481
self.leading_precision,
@@ -1025,44 +1025,9 @@ pub enum Expr {
10251025
expr: Box<Expr>,
10261026
},
10271027
/// CONVERT a value to a different data type or character encoding. e.g. `CONVERT(foo USING utf8mb4)`
1028-
// XXX too big
1029-
Convert {
1030-
/// CONVERT (false) or TRY_CONVERT (true)
1031-
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/try-convert-transact-sql?view=sql-server-ver16>
1032-
is_try: bool,
1033-
/// The expression to convert.
1034-
expr: Box<Expr>,
1035-
/// The target data type, if provided.
1036-
data_type: Option<DataType>,
1037-
/// Optional target character encoding (e.g., `utf8mb4`).
1038-
charset: Option<ObjectName>,
1039-
/// `true` when target precedes the value (MSSQL syntax).
1040-
target_before_value: bool,
1041-
/// How to translate the expression.
1042-
///
1043-
/// [MSSQL]: https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16#style
1044-
styles: Vec<Expr>,
1045-
},
1028+
Convert(Box<ConvertExpr>),
10461029
/// `CAST` an expression to a different data type e.g. `CAST(foo AS VARCHAR(123))`
1047-
// XXX too big
1048-
Cast {
1049-
/// The cast kind (e.g., `CAST`, `TRY_CAST`).
1050-
kind: CastKind,
1051-
/// Expression being cast.
1052-
expr: Box<Expr>,
1053-
/// Target data type.
1054-
data_type: DataType,
1055-
/// [MySQL] allows CAST(... AS type ARRAY) in functional index definitions for InnoDB
1056-
/// multi-valued indices. It's not really a datatype, and is only allowed in `CAST` in key
1057-
/// specifications, so it's a flag here.
1058-
///
1059-
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html#function_cast
1060-
array: bool,
1061-
/// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by [BigQuery]
1062-
///
1063-
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
1064-
format: Option<CastFormat>,
1065-
},
1030+
Cast(Box<CastExpr>),
10661031
/// AT a timestamp to a different timezone e.g. `FROM_UNIXTIME(0) AT TIME ZONE 'UTC-06:00'`
10671032
AtTimeZone {
10681033
/// Timestamp expression to shift.
@@ -1194,29 +1159,15 @@ pub enum Expr {
11941159
/// A constant of form `<data_type> 'value'`.
11951160
/// This can represent ANSI SQL `DATE`, `TIME`, and `TIMESTAMP` literals (such as `DATE '2020-01-01'`),
11961161
/// as well as constants of other types (a non-standard PostgreSQL extension).
1197-
// XXX too big
1198-
TypedString(TypedString),
1162+
TypedString(Box<TypedString>),
11991163
/// Scalar function call e.g. `LEFT(foo, 5)`
1200-
// XXX too big
12011164
Function(Box<Function>),
12021165
/// `CASE [<operand>] WHEN <condition> THEN <result> ... [ELSE <result>] END`
12031166
///
12041167
/// Note we only recognize a complete single expression as `<condition>`,
12051168
/// not `< 0` nor `1, 2, 3` as allowed in a `<simple when clause>` per
12061169
/// <https://jakewheat.github.io/sql-overview/sql-2011-foundation-grammar.html#simple-when-clause>
1207-
// XXX too big
1208-
Case {
1209-
/// The attached `CASE` token (keeps original spacing/comments).
1210-
case_token: AttachedToken,
1211-
/// The attached `END` token (keeps original spacing/comments).
1212-
end_token: AttachedToken,
1213-
/// Optional operand expression after `CASE` (for simple CASE).
1214-
operand: Option<Box<Expr>>,
1215-
/// The `WHEN ... THEN` conditions and results.
1216-
conditions: Vec<CaseWhen>,
1217-
/// Optional `ELSE` result expression.
1218-
else_result: Option<Box<Expr>>,
1219-
},
1170+
Case(Box<CaseExpr>),
12201171
/// An exists expression `[ NOT ] EXISTS(SELECT ...)`, used in expressions like
12211172
/// `WHERE [ NOT ] EXISTS (SELECT ...)`.
12221173
Exists {
@@ -1282,8 +1233,7 @@ pub enum Expr {
12821233
/// An array expression e.g. `ARRAY[1, 2]`
12831234
Array(Array),
12841235
/// An interval expression e.g. `INTERVAL '1' YEAR`
1285-
// XXX too big
1286-
Interval(Interval),
1236+
Interval(Box<Interval>),
12871237
/// `MySQL` specific text search function [(1)].
12881238
///
12891239
/// Syntax:
@@ -1334,8 +1284,7 @@ pub enum Expr {
13341284
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/functions#higher-order-functions---operator-and-lambdaparams-expr-function)
13351285
/// [Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html)
13361286
/// [DuckDB](https://duckdb.org/docs/stable/sql/functions/lambda)
1337-
// XXX too big
1338-
Lambda(LambdaFunction),
1287+
Lambda(Box<LambdaFunction>),
13391288
/// Checks membership of a value in a JSON array
13401289
MemberOf(MemberOf),
13411290
}
@@ -1357,6 +1306,68 @@ impl Expr {
13571306
}
13581307
}
13591308

1309+
/// A [`CONVERT` expression](Expr::Convert)
1310+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1311+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1312+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1313+
pub struct ConvertExpr {
1314+
/// CONVERT (false) or TRY_CONVERT (true)
1315+
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/try-convert-transact-sql?view=sql-server-ver16>
1316+
pub is_try: bool,
1317+
/// The expression to convert.
1318+
pub expr: Expr,
1319+
/// The target data type, if provided.
1320+
pub data_type: Option<DataType>,
1321+
/// Optional target character encoding (e.g., `utf8mb4`).
1322+
pub charset: Option<ObjectName>,
1323+
/// `true` when target precedes the value (MSSQL syntax).
1324+
pub target_before_value: bool,
1325+
/// How to translate the expression.
1326+
///
1327+
/// [MSSQL]: https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16#style
1328+
pub styles: Vec<Expr>,
1329+
}
1330+
1331+
/// A [`CAST` expression](Expr::Cast)
1332+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1333+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1334+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1335+
pub struct CastExpr {
1336+
/// The cast kind (e.g., `CAST`, `TRY_CAST`).
1337+
pub kind: CastKind,
1338+
/// Expression being cast.
1339+
pub expr: Expr,
1340+
/// Target data type.
1341+
pub data_type: DataType,
1342+
/// [MySQL] allows CAST(... AS type ARRAY) in functional index definitions for InnoDB
1343+
/// multi-valued indices. It's not really a datatype, and is only allowed in `CAST` in key
1344+
/// specifications, so it's a flag here.
1345+
///
1346+
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/cast-functions.html#function_cast
1347+
pub array: bool,
1348+
/// Optional CAST(string_expression AS type FORMAT format_string_expression) as used by [BigQuery]
1349+
///
1350+
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#formatting_syntax
1351+
pub format: Option<CastFormat>,
1352+
}
1353+
1354+
/// A [`CASE` expression](Expr::Case)
1355+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1356+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1357+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1358+
pub struct CaseExpr {
1359+
/// The attached `CASE` token (keeps original spacing/comments).
1360+
pub case_token: AttachedToken,
1361+
/// The attached `END` token (keeps original spacing/comments).
1362+
pub end_token: AttachedToken,
1363+
/// Optional operand expression after `CASE` (for simple CASE).
1364+
pub operand: Option<Box<Expr>>,
1365+
/// The `WHEN ... THEN` conditions and results.
1366+
pub conditions: Vec<CaseWhen>,
1367+
/// Optional `ELSE` result expression.
1368+
pub else_result: Option<Box<Expr>>,
1369+
}
1370+
13601371
/// The contents inside the `[` and `]` in a subscript expression.
13611372
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
13621373
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -1454,7 +1465,7 @@ pub struct LambdaFunction {
14541465
/// The parameters to the lambda function.
14551466
pub params: OneOrManyWithParens<LambdaFunctionParameter>,
14561467
/// The body of the lambda function.
1457-
pub body: Box<Expr>,
1468+
pub body: Expr,
14581469
/// The syntax style used to write the lambda function.
14591470
pub syntax: LambdaSyntax,
14601471
}
@@ -1909,14 +1920,15 @@ impl fmt::Display for Expr {
19091920
write!(f, "{op}{expr}")
19101921
}
19111922
}
1912-
Expr::Convert {
1913-
is_try,
1914-
expr,
1915-
target_before_value,
1916-
data_type,
1917-
charset,
1918-
styles,
1919-
} => {
1923+
Expr::Convert(convert) => {
1924+
let ConvertExpr {
1925+
is_try,
1926+
expr,
1927+
target_before_value,
1928+
data_type,
1929+
charset,
1930+
styles,
1931+
} = &**convert;
19201932
write!(f, "{}CONVERT(", if *is_try { "TRY_" } else { "" })?;
19211933
if let Some(data_type) = data_type {
19221934
if let Some(charset) = charset {
@@ -1936,40 +1948,43 @@ impl fmt::Display for Expr {
19361948
}
19371949
write!(f, ")")
19381950
}
1939-
Expr::Cast {
1940-
kind,
1941-
expr,
1942-
data_type,
1943-
array,
1944-
format,
1945-
} => match kind {
1946-
CastKind::Cast => {
1947-
write!(f, "CAST({expr} AS {data_type}")?;
1948-
if *array {
1949-
write!(f, " ARRAY")?;
1951+
Expr::Cast(cast) => {
1952+
let CastExpr {
1953+
kind,
1954+
expr,
1955+
data_type,
1956+
array,
1957+
format,
1958+
} = &**cast;
1959+
match kind {
1960+
CastKind::Cast => {
1961+
write!(f, "CAST({expr} AS {data_type}")?;
1962+
if *array {
1963+
write!(f, " ARRAY")?;
1964+
}
1965+
if let Some(format) = format {
1966+
write!(f, " FORMAT {format}")?;
1967+
}
1968+
write!(f, ")")
19501969
}
1951-
if let Some(format) = format {
1952-
write!(f, " FORMAT {format}")?;
1970+
CastKind::TryCast => {
1971+
if let Some(format) = format {
1972+
write!(f, "TRY_CAST({expr} AS {data_type} FORMAT {format})")
1973+
} else {
1974+
write!(f, "TRY_CAST({expr} AS {data_type})")
1975+
}
19531976
}
1954-
write!(f, ")")
1955-
}
1956-
CastKind::TryCast => {
1957-
if let Some(format) = format {
1958-
write!(f, "TRY_CAST({expr} AS {data_type} FORMAT {format})")
1959-
} else {
1960-
write!(f, "TRY_CAST({expr} AS {data_type})")
1977+
CastKind::SafeCast => {
1978+
if let Some(format) = format {
1979+
write!(f, "SAFE_CAST({expr} AS {data_type} FORMAT {format})")
1980+
} else {
1981+
write!(f, "SAFE_CAST({expr} AS {data_type})")
1982+
}
19611983
}
1962-
}
1963-
CastKind::SafeCast => {
1964-
if let Some(format) = format {
1965-
write!(f, "SAFE_CAST({expr} AS {data_type} FORMAT {format})")
1966-
} else {
1967-
write!(f, "SAFE_CAST({expr} AS {data_type})")
1984+
CastKind::DoubleColon => {
1985+
write!(f, "{expr}::{data_type}")
19681986
}
19691987
}
1970-
CastKind::DoubleColon => {
1971-
write!(f, "{expr}::{data_type}")
1972-
}
19731988
},
19741989
Expr::Extract {
19751990
field,
@@ -2000,13 +2015,14 @@ impl fmt::Display for Expr {
20002015
Expr::Prefixed { prefix, value } => write!(f, "{prefix} {value}"),
20012016
Expr::TypedString(ts) => ts.fmt(f),
20022017
Expr::Function(fun) => fun.fmt(f),
2003-
Expr::Case {
2004-
case_token: _,
2005-
end_token: _,
2006-
operand,
2007-
conditions,
2008-
else_result,
2009-
} => {
2018+
Expr::Case(case) => {
2019+
let CaseExpr {
2020+
case_token: _,
2021+
end_token: _,
2022+
operand,
2023+
conditions,
2024+
else_result,
2025+
} = &**case;
20102026
f.write_str("CASE")?;
20112027
if let Some(operand) = operand {
20122028
f.write_str(" ")?;
@@ -12343,28 +12359,28 @@ mod tests {
1234312359
#[test]
1234412360
fn test_interval_display() {
1234512361
let interval = Expr::Interval(Interval {
12346-
value: Box::new(Expr::Value(
12362+
value: Expr::Value(
1234712363
Value::SingleQuotedString(String::from("123:45.67")).with_empty_span(),
12348-
)),
12364+
),
1234912365
leading_field: Some(DateTimeField::Minute),
1235012366
leading_precision: Some(10),
1235112367
last_field: Some(DateTimeField::Second),
1235212368
fractional_seconds_precision: Some(9),
12353-
});
12369+
}.into());
1235412370
assert_eq!(
1235512371
"INTERVAL '123:45.67' MINUTE (10) TO SECOND (9)",
1235612372
format!("{interval}"),
1235712373
);
1235812374

1235912375
let interval = Expr::Interval(Interval {
12360-
value: Box::new(Expr::Value(
12376+
value: Expr::Value(
1236112377
Value::SingleQuotedString(String::from("5")).with_empty_span(),
12362-
)),
12378+
),
1236312379
leading_field: Some(DateTimeField::Second),
1236412380
leading_precision: Some(1),
1236512381
last_field: None,
1236612382
fractional_seconds_precision: Some(3),
12367-
});
12383+
}.into());
1236812384
assert_eq!("INTERVAL '5' SECOND (1, 3)", format!("{interval}"));
1236912385
}
1237012386

0 commit comments

Comments
 (0)