Skip to content

Commit b428b0f

Browse files
committed
Preserve MySQL-style LIMIT <offset>, <limit> syntax
We already parse it, but were rewriting it into standard `LIMIT <limit> OFFSET <offset>` syntax. Now we preserve the original syntax.
1 parent 6ec5223 commit b428b0f

10 files changed

+266
-230
lines changed

src/ast/mod.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,18 @@ pub use self::query::{
6666
FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem,
6767
InputFormatClause, Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator,
6868
JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn, JsonTableNestedColumn,
69-
LateralView, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure,
70-
NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OpenJsonTableColumn,
71-
OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PivotValueSource, ProjectionSelect, Query,
72-
RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch,
73-
Select, SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SetExpr,
74-
SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableAliasColumnDef,
75-
TableFactor, TableFunctionArgs, TableIndexHintForClause, TableIndexHintType, TableIndexHints,
76-
TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod,
77-
TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
78-
TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind,
79-
ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
69+
LateralView, LimitClause, LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol,
70+
Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows,
71+
OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PivotValueSource,
72+
ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
73+
ReplaceSelectItem, RowsPerMatch, Select, SelectFlavor, SelectInto, SelectItem,
74+
SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier, Setting,
75+
SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs,
76+
TableIndexHintForClause, TableIndexHintType, TableIndexHints, TableIndexType, TableSample,
77+
TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
78+
TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
79+
TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
80+
WildcardAdditionalOptions, With, WithFill,
8081
};
8182

8283
pub use self::trigger::{

src/ast/query.rs

+56-16
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,8 @@ pub struct Query {
4343
pub body: Box<SetExpr>,
4444
/// ORDER BY
4545
pub order_by: Option<OrderBy>,
46-
/// `LIMIT { <N> | ALL }`
47-
pub limit: Option<Expr>,
48-
49-
/// `LIMIT { <N> } BY { <expr>,<expr>,... } }`
50-
pub limit_by: Vec<Expr>,
51-
52-
/// `OFFSET <N> [ { ROW | ROWS } ]`
53-
pub offset: Option<Offset>,
46+
/// `LIMIT ... OFFSET ... | LIMIT <offset>, <limit>`
47+
pub limit_clause: Option<LimitClause>,
5448
/// `FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH TIES }`
5549
pub fetch: Option<Fetch>,
5650
/// `FOR { UPDATE | SHARE } [ OF table_name ] [ SKIP LOCKED | NOWAIT ]`
@@ -79,14 +73,9 @@ impl fmt::Display for Query {
7973
if let Some(ref order_by) = self.order_by {
8074
write!(f, " {order_by}")?;
8175
}
82-
if let Some(ref limit) = self.limit {
83-
write!(f, " LIMIT {limit}")?;
84-
}
85-
if let Some(ref offset) = self.offset {
86-
write!(f, " {offset}")?;
87-
}
88-
if !self.limit_by.is_empty() {
89-
write!(f, " BY {}", display_separated(&self.limit_by, ", "))?;
76+
77+
if let Some(ref limit_clause) = self.limit_clause {
78+
limit_clause.fmt(f)?;
9079
}
9180
if let Some(ref settings) = self.settings {
9281
write!(f, " SETTINGS {}", display_comma_separated(settings))?;
@@ -2372,6 +2361,57 @@ impl fmt::Display for OrderByOptions {
23722361
}
23732362
}
23742363

2364+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2365+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2366+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
2367+
pub enum LimitClause {
2368+
/// Standard SQL syntax
2369+
///
2370+
/// `LIMIT <limit> [BY <expr>,<expr>,...] [OFFSET <offset>]`
2371+
LimitOffset {
2372+
/// `LIMIT { <N> | ALL }`
2373+
limit: Option<Expr>,
2374+
/// `OFFSET <N> [ { ROW | ROWS } ]`
2375+
offset: Option<Offset>,
2376+
/// `BY { <expr>,<expr>,... } }`
2377+
///
2378+
/// [ClickHouse](https://clickhouse.com/docs/sql-reference/statements/select/limit-by)
2379+
limit_by: Vec<Expr>,
2380+
},
2381+
/// [MySQL]-specific syntax; the order of expressions is reversed.
2382+
///
2383+
/// `LIMIT <offset>, <limit>`
2384+
///
2385+
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/select.html
2386+
OffsetCommaLimit { offset: Expr, limit: Expr },
2387+
}
2388+
2389+
impl fmt::Display for LimitClause {
2390+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2391+
match self {
2392+
LimitClause::LimitOffset {
2393+
limit,
2394+
limit_by,
2395+
offset,
2396+
} => {
2397+
if let Some(ref limit) = limit {
2398+
write!(f, " LIMIT {limit}")?;
2399+
}
2400+
if let Some(ref offset) = offset {
2401+
write!(f, " {offset}")?;
2402+
}
2403+
if !limit_by.is_empty() {
2404+
write!(f, " BY {}", display_separated(limit_by, ", "))?;
2405+
}
2406+
Ok(())
2407+
}
2408+
LimitClause::OffsetCommaLimit { offset, limit } => {
2409+
write!(f, " LIMIT {}, {}", offset, limit)
2410+
}
2411+
}
2412+
}
2413+
}
2414+
23752415
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
23762416
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23772417
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/ast/spans.rs

+23-8
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ use super::{
2929
Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList,
3030
FunctionArguments, GroupByExpr, HavingBound, IlikeSelectItem, Insert, Interpolate,
3131
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView,
32-
MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset,
33-
OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition,
32+
LimitClause, MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName, ObjectNamePart,
33+
Offset, OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind, Partition,
3434
PivotValueSource, ProjectionSelect, Query, ReferentialAction, RenameSelectItem,
3535
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption,
3636
Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint,
@@ -94,9 +94,7 @@ impl Spanned for Query {
9494
with,
9595
body,
9696
order_by,
97-
limit,
98-
limit_by,
99-
offset,
97+
limit_clause,
10098
fetch,
10199
locks: _, // todo
102100
for_clause: _, // todo, mssql specific
@@ -109,14 +107,31 @@ impl Spanned for Query {
109107
.map(|i| i.span())
110108
.chain(core::iter::once(body.span()))
111109
.chain(order_by.as_ref().map(|i| i.span()))
112-
.chain(limit.as_ref().map(|i| i.span()))
113-
.chain(limit_by.iter().map(|i| i.span()))
114-
.chain(offset.as_ref().map(|i| i.span()))
110+
.chain(limit_clause.as_ref().map(|i| i.span()))
115111
.chain(fetch.as_ref().map(|i| i.span())),
116112
)
117113
}
118114
}
119115

116+
impl Spanned for LimitClause {
117+
fn span(&self) -> Span {
118+
match self {
119+
LimitClause::LimitOffset {
120+
limit,
121+
offset,
122+
limit_by,
123+
} => union_spans(
124+
limit
125+
.iter()
126+
.map(|i| i.span())
127+
.chain(offset.as_ref().map(|i| i.span()))
128+
.chain(limit_by.iter().map(|i| i.span())),
129+
),
130+
LimitClause::OffsetCommaLimit { offset, limit } => offset.span().union(&limit.span()),
131+
}
132+
}
133+
}
134+
120135
impl Spanned for Offset {
121136
fn span(&self) -> Span {
122137
let Offset {

src/ast/visitor.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ where
523523
/// // Remove all select limits in sub-queries
524524
/// visit_expressions_mut(&mut statements, |expr| {
525525
/// if let Expr::Subquery(q) = expr {
526-
/// q.limit = None
526+
/// q.limit_clause = None;
527527
/// }
528528
/// ControlFlow::<()>::Continue(())
529529
/// });
@@ -647,7 +647,7 @@ where
647647
/// // Remove all select limits in outer statements (not in sub-queries)
648648
/// visit_statements_mut(&mut statements, |stmt| {
649649
/// if let Statement::Query(q) = stmt {
650-
/// q.limit = None
650+
/// q.limit_clause = None;
651651
/// }
652652
/// ControlFlow::<()>::Continue(())
653653
/// });

src/parser/mod.rs

+25-19
Original file line numberDiff line numberDiff line change
@@ -10222,10 +10222,8 @@ impl<'a> Parser<'a> {
1022210222
Ok(Query {
1022310223
with,
1022410224
body: self.parse_insert_setexpr_boxed()?,
10225-
limit: None,
10226-
limit_by: vec![],
1022710225
order_by: None,
10228-
offset: None,
10226+
limit_clause: None,
1022910227
fetch: None,
1023010228
locks: vec![],
1023110229
for_clause: None,
@@ -10237,10 +10235,8 @@ impl<'a> Parser<'a> {
1023710235
Ok(Query {
1023810236
with,
1023910237
body: self.parse_update_setexpr_boxed()?,
10240-
limit: None,
10241-
limit_by: vec![],
1024210238
order_by: None,
10243-
offset: None,
10239+
limit_clause: None,
1024410240
fetch: None,
1024510241
locks: vec![],
1024610242
for_clause: None,
@@ -10255,6 +10251,7 @@ impl<'a> Parser<'a> {
1025510251

1025610252
let mut limit = None;
1025710253
let mut offset = None;
10254+
let mut limit_comma_offset = None;
1025810255

1025910256
for _x in 0..2 {
1026010257
if limit.is_none() && self.parse_keyword(Keyword::LIMIT) {
@@ -10272,20 +10269,33 @@ impl<'a> Parser<'a> {
1027210269
{
1027310270
// MySQL style LIMIT x,y => LIMIT y OFFSET x.
1027410271
// Check <https://dev.mysql.com/doc/refman/8.0/en/select.html> for more details.
10275-
offset = Some(Offset {
10276-
value: limit.unwrap(),
10277-
rows: OffsetRows::None,
10278-
});
10272+
limit_comma_offset = limit.take();
1027910273
limit = Some(self.parse_expr()?);
1028010274
}
1028110275
}
1028210276

10283-
let limit_by = if dialect_of!(self is ClickHouseDialect | GenericDialect)
10277+
let limit_by = if limit_comma_offset.is_none()
10278+
&& dialect_of!(self is ClickHouseDialect | GenericDialect)
1028410279
&& self.parse_keyword(Keyword::BY)
1028510280
{
10286-
self.parse_comma_separated(Parser::parse_expr)?
10281+
Some(self.parse_comma_separated(Parser::parse_expr)?)
1028710282
} else {
10288-
vec![]
10283+
None
10284+
};
10285+
10286+
let limit_clause = if let Some(offset) = limit_comma_offset {
10287+
Some(LimitClause::OffsetCommaLimit {
10288+
offset,
10289+
limit: limit.expect("Expected <limit> expression for LIMIT <offset>, <limit>"),
10290+
})
10291+
} else if limit.is_some() || offset.is_some() || limit_by.is_some() {
10292+
Some(LimitClause::LimitOffset {
10293+
limit,
10294+
offset,
10295+
limit_by: limit_by.unwrap_or_default(),
10296+
})
10297+
} else {
10298+
None
1028910299
};
1029010300

1029110301
let settings = self.parse_settings()?;
@@ -10323,9 +10333,7 @@ impl<'a> Parser<'a> {
1032310333
with,
1032410334
body,
1032510335
order_by,
10326-
limit,
10327-
limit_by,
10328-
offset,
10336+
limit_clause,
1032910337
fetch,
1033010338
locks,
1033110339
for_clause,
@@ -11680,9 +11688,7 @@ impl<'a> Parser<'a> {
1168011688
with: None,
1168111689
body: Box::new(values),
1168211690
order_by: None,
11683-
limit: None,
11684-
limit_by: vec![],
11685-
offset: None,
11691+
limit_clause: None,
1168611692
fetch: None,
1168711693
locks: vec![],
1168811694
for_clause: None,

tests/sqlparser_clickhouse.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,14 @@ fn parse_select_order_by_with_fill_interpolate() {
11071107
},
11081108
select.order_by.expect("ORDER BY expected")
11091109
);
1110-
assert_eq!(Some(Expr::value(number("2"))), select.limit);
1110+
assert_eq!(
1111+
select.limit_clause,
1112+
Some(LimitClause::LimitOffset {
1113+
limit: Some(Expr::value(number("2"))),
1114+
offset: None,
1115+
limit_by: vec![]
1116+
})
1117+
);
11111118
}
11121119

11131120
#[test]

0 commit comments

Comments
 (0)