This is an automated email from the ASF dual-hosted git repository.
iffyio pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new fb578bb4 Preserve MySQL-style `LIMIT <offset>, <limit>` syntax (#1765)
fb578bb4 is described below
commit fb578bb419d08d2d5b49fb75a61f1ddd6df77ba4
Author: Michael Victor Zink <[email protected]>
AuthorDate: Wed Mar 12 13:24:06 2025 -0700
Preserve MySQL-style `LIMIT <offset>, <limit>` syntax (#1765)
---
src/ast/mod.rs | 23 ++--
src/ast/query.rs | 73 ++++++++++---
src/ast/spans.rs | 31 ++++--
src/ast/visitor.rs | 4 +-
src/parser/mod.rs | 109 ++++++++++---------
tests/sqlparser_clickhouse.rs | 15 ++-
tests/sqlparser_common.rs | 240 +++++++++++++++++++++++-------------------
tests/sqlparser_mssql.rs | 12 +--
tests/sqlparser_mysql.rs | 66 +++---------
tests/sqlparser_postgres.rs | 20 +---
10 files changed, 327 insertions(+), 266 deletions(-)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 8ab3fc0f..139850e8 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -66,17 +66,18 @@ pub use self::query::{
FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias,
IlikeSelectItem,
InputFormatClause, Interpolate, InterpolateExpr, Join, JoinConstraint,
JoinOperator,
JsonTableColumn, JsonTableColumnErrorHandling, JsonTableNamedColumn,
JsonTableNestedColumn,
- LateralView, LockClause, LockType, MatchRecognizePattern,
MatchRecognizeSymbol, Measure,
- NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows,
OpenJsonTableColumn,
- OrderBy, OrderByExpr, OrderByKind, OrderByOptions, PivotValueSource,
ProjectionSelect, Query,
- RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
ReplaceSelectItem, RowsPerMatch,
- Select, SelectFlavor, SelectInto, SelectItem,
SelectItemQualifiedWildcardKind, SetExpr,
- SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, TableAlias,
TableAliasColumnDef,
- TableFactor, TableFunctionArgs, TableIndexHintForClause,
TableIndexHintType, TableIndexHints,
- TableIndexType, TableSample, TableSampleBucket, TableSampleKind,
TableSampleMethod,
- TableSampleModifier, TableSampleQuantity, TableSampleSeed,
TableSampleSeedModifier,
- TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity,
UpdateTableFromKind,
- ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
+ LateralView, LimitClause, LockClause, LockType, MatchRecognizePattern,
MatchRecognizeSymbol,
+ Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset,
OffsetRows,
+ OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions,
PivotValueSource,
+ ProjectionSelect, Query, RenameSelectItem, RepetitionQuantifier,
ReplaceSelectElement,
+ ReplaceSelectItem, RowsPerMatch, Select, SelectFlavor, SelectInto,
SelectItem,
+ SelectItemQualifiedWildcardKind, SetExpr, SetOperator, SetQuantifier,
Setting,
+ SymbolDefinition, Table, TableAlias, TableAliasColumnDef, TableFactor,
TableFunctionArgs,
+ TableIndexHintForClause, TableIndexHintType, TableIndexHints,
TableIndexType, TableSample,
+ TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
+ TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
TableSampleUnit, TableVersion,
+ TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode,
Values,
+ WildcardAdditionalOptions, With, WithFill,
};
pub use self::trigger::{
diff --git a/src/ast/query.rs b/src/ast/query.rs
index 12f72932..1b30dcf1 100644
--- a/src/ast/query.rs
+++ b/src/ast/query.rs
@@ -43,14 +43,8 @@ pub struct Query {
pub body: Box<SetExpr>,
/// ORDER BY
pub order_by: Option<OrderBy>,
- /// `LIMIT { <N> | ALL }`
- pub limit: Option<Expr>,
-
- /// `LIMIT { <N> } BY { <expr>,<expr>,... } }`
- pub limit_by: Vec<Expr>,
-
- /// `OFFSET <N> [ { ROW | ROWS } ]`
- pub offset: Option<Offset>,
+ /// `LIMIT ... OFFSET ... | LIMIT <offset>, <limit>`
+ pub limit_clause: Option<LimitClause>,
/// `FETCH { FIRST | NEXT } <N> [ PERCENT ] { ROW | ROWS } | { ONLY | WITH
TIES }`
pub fetch: Option<Fetch>,
/// `FOR { UPDATE | SHARE } [ OF table_name ] [ SKIP LOCKED | NOWAIT ]`
@@ -79,14 +73,9 @@ impl fmt::Display for Query {
if let Some(ref order_by) = self.order_by {
write!(f, " {order_by}")?;
}
- if let Some(ref limit) = self.limit {
- write!(f, " LIMIT {limit}")?;
- }
- if let Some(ref offset) = self.offset {
- write!(f, " {offset}")?;
- }
- if !self.limit_by.is_empty() {
- write!(f, " BY {}", display_separated(&self.limit_by, ", "))?;
+
+ if let Some(ref limit_clause) = self.limit_clause {
+ limit_clause.fmt(f)?;
}
if let Some(ref settings) = self.settings {
write!(f, " SETTINGS {}", display_comma_separated(settings))?;
@@ -2374,6 +2363,58 @@ impl fmt::Display for OrderByOptions {
}
}
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum LimitClause {
+ /// Standard SQL syntax
+ ///
+ /// `LIMIT <limit> [BY <expr>,<expr>,...] [OFFSET <offset>]`
+ LimitOffset {
+ /// `LIMIT { <N> | ALL }`
+ limit: Option<Expr>,
+ /// `OFFSET <N> [ { ROW | ROWS } ]`
+ offset: Option<Offset>,
+ /// `BY { <expr>,<expr>,... } }`
+ ///
+ ///
[ClickHouse](https://clickhouse.com/docs/sql-reference/statements/select/limit-by)
+ limit_by: Vec<Expr>,
+ },
+ /// [MySQL]-specific syntax; the order of expressions is reversed.
+ ///
+ /// `LIMIT <offset>, <limit>`
+ ///
+ /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/select.html
+ OffsetCommaLimit { offset: Expr, limit: Expr },
+}
+
+impl fmt::Display for LimitClause {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ LimitClause::LimitOffset {
+ limit,
+ limit_by,
+ offset,
+ } => {
+ if let Some(ref limit) = limit {
+ write!(f, " LIMIT {limit}")?;
+ }
+ if let Some(ref offset) = offset {
+ write!(f, " {offset}")?;
+ }
+ if !limit_by.is_empty() {
+ debug_assert!(limit.is_some());
+ write!(f, " BY {}", display_separated(limit_by, ", "))?;
+ }
+ Ok(())
+ }
+ LimitClause::OffsetCommaLimit { offset, limit } => {
+ write!(f, " LIMIT {}, {}", offset, limit)
+ }
+ }
+ }
+}
+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index fb0fc3f3..a4f5eb46 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -29,8 +29,8 @@ use super::{
Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause,
FunctionArgumentList,
FunctionArguments, GroupByExpr, HavingBound, IlikeSelectItem, Insert,
Interpolate,
InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath,
JsonPathElem, LateralView,
- MatchRecognizePattern, Measure, NamedWindowDefinition, ObjectName,
ObjectNamePart, Offset,
- OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr, OrderByKind,
Partition,
+ LimitClause, MatchRecognizePattern, Measure, NamedWindowDefinition,
ObjectName, ObjectNamePart,
+ Offset, OnConflict, OnConflictAction, OnInsert, OrderBy, OrderByExpr,
OrderByKind, Partition,
PivotValueSource, ProjectionSelect, Query, ReferentialAction,
RenameSelectItem,
ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem,
SetExpr, SqlOption,
Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef,
TableConstraint,
@@ -94,9 +94,7 @@ impl Spanned for Query {
with,
body,
order_by,
- limit,
- limit_by,
- offset,
+ limit_clause,
fetch,
locks: _, // todo
for_clause: _, // todo, mssql specific
@@ -109,14 +107,31 @@ impl Spanned for Query {
.map(|i| i.span())
.chain(core::iter::once(body.span()))
.chain(order_by.as_ref().map(|i| i.span()))
- .chain(limit.as_ref().map(|i| i.span()))
- .chain(limit_by.iter().map(|i| i.span()))
- .chain(offset.as_ref().map(|i| i.span()))
+ .chain(limit_clause.as_ref().map(|i| i.span()))
.chain(fetch.as_ref().map(|i| i.span())),
)
}
}
+impl Spanned for LimitClause {
+ fn span(&self) -> Span {
+ match self {
+ LimitClause::LimitOffset {
+ limit,
+ offset,
+ limit_by,
+ } => union_spans(
+ limit
+ .iter()
+ .map(|i| i.span())
+ .chain(offset.as_ref().map(|i| i.span()))
+ .chain(limit_by.iter().map(|i| i.span())),
+ ),
+ LimitClause::OffsetCommaLimit { offset, limit } =>
offset.span().union(&limit.span()),
+ }
+ }
+}
+
impl Spanned for Offset {
fn span(&self) -> Span {
let Offset {
diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs
index a5d355fe..50985a3e 100644
--- a/src/ast/visitor.rs
+++ b/src/ast/visitor.rs
@@ -523,7 +523,7 @@ where
/// // Remove all select limits in sub-queries
/// visit_expressions_mut(&mut statements, |expr| {
/// if let Expr::Subquery(q) = expr {
-/// q.limit = None
+/// q.limit_clause = None;
/// }
/// ControlFlow::<()>::Continue(())
/// });
@@ -647,7 +647,7 @@ where
/// // Remove all select limits in outer statements (not in sub-queries)
/// visit_statements_mut(&mut statements, |stmt| {
/// if let Statement::Query(q) = stmt {
-/// q.limit = None
+/// q.limit_clause = None;
/// }
/// ControlFlow::<()>::Continue(())
/// });
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 32a7bccd..d3c48a6e 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -9491,6 +9491,60 @@ impl<'a> Parser<'a> {
}
}
+ fn parse_optional_limit_clause(&mut self) -> Result<Option<LimitClause>,
ParserError> {
+ let mut offset = if self.parse_keyword(Keyword::OFFSET) {
+ Some(self.parse_offset()?)
+ } else {
+ None
+ };
+
+ let (limit, limit_by) = if self.parse_keyword(Keyword::LIMIT) {
+ let expr = self.parse_limit()?;
+
+ if self.dialect.supports_limit_comma()
+ && offset.is_none()
+ && expr.is_some() // ALL not supported with comma
+ && self.consume_token(&Token::Comma)
+ {
+ let offset = expr.ok_or_else(|| {
+ ParserError::ParserError(
+ "Missing offset for LIMIT <offset>,
<limit>".to_string(),
+ )
+ })?;
+ return Ok(Some(LimitClause::OffsetCommaLimit {
+ offset,
+ limit: self.parse_expr()?,
+ }));
+ }
+
+ let limit_by = if dialect_of!(self is ClickHouseDialect |
GenericDialect)
+ && self.parse_keyword(Keyword::BY)
+ {
+ Some(self.parse_comma_separated(Parser::parse_expr)?)
+ } else {
+ None
+ };
+
+ (Some(expr), limit_by)
+ } else {
+ (None, None)
+ };
+
+ if offset.is_none() && limit.is_some() &&
self.parse_keyword(Keyword::OFFSET) {
+ offset = Some(self.parse_offset()?);
+ }
+
+ if offset.is_some() || (limit.is_some() && limit != Some(None)) ||
limit_by.is_some() {
+ Ok(Some(LimitClause::LimitOffset {
+ limit: limit.unwrap_or_default(),
+ offset,
+ limit_by: limit_by.unwrap_or_default(),
+ }))
+ } else {
+ Ok(None)
+ }
+ }
+
/// Parse a table object for insertion
/// e.g. `some_database.some_table` or `FUNCTION some_table_func(...)`
pub fn parse_table_object(&mut self) -> Result<TableObject, ParserError> {
@@ -10231,10 +10285,8 @@ impl<'a> Parser<'a> {
Ok(Query {
with,
body: self.parse_insert_setexpr_boxed()?,
- limit: None,
- limit_by: vec![],
order_by: None,
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -10246,10 +10298,8 @@ impl<'a> Parser<'a> {
Ok(Query {
with,
body: self.parse_update_setexpr_boxed()?,
- limit: None,
- limit_by: vec![],
order_by: None,
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -10261,10 +10311,8 @@ impl<'a> Parser<'a> {
Ok(Query {
with,
body: self.parse_delete_setexpr_boxed()?,
- limit: None,
- limit_by: vec![],
+ limit_clause: None,
order_by: None,
- offset: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -10277,40 +10325,7 @@ impl<'a> Parser<'a> {
let order_by = self.parse_optional_order_by()?;
- let mut limit = None;
- let mut offset = None;
-
- for _x in 0..2 {
- if limit.is_none() && self.parse_keyword(Keyword::LIMIT) {
- limit = self.parse_limit()?
- }
-
- if offset.is_none() && self.parse_keyword(Keyword::OFFSET) {
- offset = Some(self.parse_offset()?)
- }
-
- if self.dialect.supports_limit_comma()
- && limit.is_some()
- && offset.is_none()
- && self.consume_token(&Token::Comma)
- {
- // MySQL style LIMIT x,y => LIMIT y OFFSET x.
- // Check
<https://dev.mysql.com/doc/refman/8.0/en/select.html> for more details.
- offset = Some(Offset {
- value: limit.unwrap(),
- rows: OffsetRows::None,
- });
- limit = Some(self.parse_expr()?);
- }
- }
-
- let limit_by = if dialect_of!(self is ClickHouseDialect |
GenericDialect)
- && self.parse_keyword(Keyword::BY)
- {
- self.parse_comma_separated(Parser::parse_expr)?
- } else {
- vec![]
- };
+ let limit_clause = self.parse_optional_limit_clause()?;
let settings = self.parse_settings()?;
@@ -10347,9 +10362,7 @@ impl<'a> Parser<'a> {
with,
body,
order_by,
- limit,
- limit_by,
- offset,
+ limit_clause,
fetch,
locks,
for_clause,
@@ -11809,9 +11822,7 @@ impl<'a> Parser<'a> {
with: None,
body: Box::new(values),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs
index 72a64a48..c56f9886 100644
--- a/tests/sqlparser_clickhouse.rs
+++ b/tests/sqlparser_clickhouse.rs
@@ -944,6 +944,12 @@ fn parse_limit_by() {
clickhouse_and_generic().verified_stmt(
r#"SELECT * FROM default.last_asset_runs_mv ORDER BY created_at DESC
LIMIT 1 BY asset, toStartOfDay(created_at)"#,
);
+ clickhouse_and_generic().parse_sql_statements(
+ r#"SELECT * FROM default.last_asset_runs_mv ORDER BY created_at DESC
BY asset, toStartOfDay(created_at)"#,
+ ).expect_err("BY without LIMIT");
+ clickhouse_and_generic()
+ .parse_sql_statements("SELECT * FROM T OFFSET 5 BY foo")
+ .expect_err("BY with OFFSET but without LIMIT");
}
#[test]
@@ -1107,7 +1113,14 @@ fn parse_select_order_by_with_fill_interpolate() {
},
select.order_by.expect("ORDER BY expected")
);
- assert_eq!(Some(Expr::value(number("2"))), select.limit);
+ assert_eq!(
+ select.limit_clause,
+ Some(LimitClause::LimitOffset {
+ limit: Some(Expr::value(number("2"))),
+ offset: None,
+ limit_by: vec![]
+ })
+ );
}
#[test]
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index c7bf287c..b5d42ea6 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -483,9 +483,7 @@ fn parse_update_set_from() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -900,7 +898,12 @@ fn parse_simple_select() {
assert!(select.distinct.is_none());
assert_eq!(3, select.projection.len());
let select = verified_query(sql);
- assert_eq!(Some(Expr::value(number("5"))), select.limit);
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: Some(Expr::value(number("5"))),
+ offset: None,
+ limit_by: vec![],
+ };
+ assert_eq!(Some(expected_limit_clause), select.limit_clause);
}
#[test]
@@ -908,14 +911,31 @@ fn parse_limit() {
verified_stmt("SELECT * FROM user LIMIT 1");
}
+#[test]
+fn parse_invalid_limit_by() {
+ all_dialects()
+ .parse_sql_statements("SELECT * FROM user BY name")
+ .expect_err("BY without LIMIT");
+}
+
#[test]
fn parse_limit_is_not_an_alias() {
// In dialects supporting LIMIT it shouldn't be parsed as a table alias
let ast = verified_query("SELECT id FROM customer LIMIT 1");
- assert_eq!(Some(Expr::value(number("1"))), ast.limit);
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: Some(Expr::value(number("1"))),
+ offset: None,
+ limit_by: vec![],
+ };
+ assert_eq!(Some(expected_limit_clause), ast.limit_clause);
let ast = verified_query("SELECT 1 LIMIT 5");
- assert_eq!(Some(Expr::value(number("5"))), ast.limit);
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: Some(Expr::value(number("5"))),
+ offset: None,
+ limit_by: vec![],
+ };
+ assert_eq!(Some(expected_limit_clause), ast.limit_clause);
}
#[test]
@@ -2493,7 +2513,12 @@ fn parse_select_order_by_limit() {
]),
select.order_by.expect("ORDER BY expected").kind
);
- assert_eq!(Some(Expr::value(number("2"))), select.limit);
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: Some(Expr::value(number("2"))),
+ offset: None,
+ limit_by: vec![],
+ };
+ assert_eq!(Some(expected_limit_clause), select.limit_clause);
}
#[test]
@@ -2654,7 +2679,12 @@ fn parse_select_order_by_nulls_order() {
]),
select.order_by.expect("ORDER BY expeccted").kind
);
- assert_eq!(Some(Expr::value(number("2"))), select.limit);
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: Some(Expr::value(number("2"))),
+ offset: None,
+ limit_by: vec![],
+ };
+ assert_eq!(Some(expected_limit_clause), select.limit_clause);
}
#[test]
@@ -2864,6 +2894,14 @@ fn parse_limit_accepts_all() {
"SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT ALL",
"SELECT id, fname, lname FROM customer WHERE id = 1",
);
+ one_statement_parses_to(
+ "SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT ALL OFFSET
1",
+ "SELECT id, fname, lname FROM customer WHERE id = 1 OFFSET 1",
+ );
+ one_statement_parses_to(
+ "SELECT id, fname, lname FROM customer WHERE id = 1 OFFSET 1 LIMIT
ALL",
+ "SELECT id, fname, lname FROM customer WHERE id = 1 OFFSET 1",
+ );
}
#[test]
@@ -4247,9 +4285,7 @@ fn parse_create_table_as_table() {
schema_name: None,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -4274,9 +4310,7 @@ fn parse_create_table_as_table() {
schema_name: Some("schema_name".to_string()),
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -6273,9 +6307,7 @@ fn parse_interval_and_or_xor() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -8175,55 +8207,65 @@ fn parse_offset() {
let dialects =
all_dialects_where(|d| !d.is_column_alias(&Keyword::OFFSET, &mut
Parser::new(d)));
- let expect = Some(Offset {
- value: Expr::value(number("2")),
- rows: OffsetRows::Rows,
+ let expected_limit_clause = &Some(LimitClause::LimitOffset {
+ limit: None,
+ offset: Some(Offset {
+ value: Expr::value(number("2")),
+ rows: OffsetRows::Rows,
+ }),
+ limit_by: vec![],
});
let ast = dialects.verified_query("SELECT foo FROM bar OFFSET 2 ROWS");
- assert_eq!(ast.offset, expect);
+ assert_eq!(&ast.limit_clause, expected_limit_clause);
let ast = dialects.verified_query("SELECT foo FROM bar WHERE foo = 4
OFFSET 2 ROWS");
- assert_eq!(ast.offset, expect);
+ assert_eq!(&ast.limit_clause, expected_limit_clause);
let ast = dialects.verified_query("SELECT foo FROM bar ORDER BY baz OFFSET
2 ROWS");
- assert_eq!(ast.offset, expect);
+ assert_eq!(&ast.limit_clause, expected_limit_clause);
let ast =
dialects.verified_query("SELECT foo FROM bar WHERE foo = 4 ORDER BY
baz OFFSET 2 ROWS");
- assert_eq!(ast.offset, expect);
+ assert_eq!(&ast.limit_clause, expected_limit_clause);
let ast =
dialects.verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2
ROWS) OFFSET 2 ROWS");
- assert_eq!(ast.offset, expect);
+ assert_eq!(&ast.limit_clause, expected_limit_clause);
match *ast.body {
SetExpr::Select(s) => match only(s.from).relation {
TableFactor::Derived { subquery, .. } => {
- assert_eq!(subquery.offset, expect);
+ assert_eq!(&subquery.limit_clause, expected_limit_clause);
}
_ => panic!("Test broke"),
},
_ => panic!("Test broke"),
}
- let ast = dialects.verified_query("SELECT 'foo' OFFSET 0 ROWS");
- assert_eq!(
- ast.offset,
- Some(Offset {
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: None,
+ offset: Some(Offset {
value: Expr::value(number("0")),
rows: OffsetRows::Rows,
- })
- );
- let ast = dialects.verified_query("SELECT 'foo' OFFSET 1 ROW");
- assert_eq!(
- ast.offset,
- Some(Offset {
+ }),
+ limit_by: vec![],
+ };
+ let ast = dialects.verified_query("SELECT 'foo' OFFSET 0 ROWS");
+ assert_eq!(ast.limit_clause, Some(expected_limit_clause));
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: None,
+ offset: Some(Offset {
value: Expr::value(number("1")),
rows: OffsetRows::Row,
- })
- );
- let ast = dialects.verified_query("SELECT 'foo' OFFSET 1");
- assert_eq!(
- ast.offset,
- Some(Offset {
- value: Expr::value(number("1")),
+ }),
+ limit_by: vec![],
+ };
+ let ast = dialects.verified_query("SELECT 'foo' OFFSET 1 ROW");
+ assert_eq!(ast.limit_clause, Some(expected_limit_clause));
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: None,
+ offset: Some(Offset {
+ value: Expr::value(number("2")),
rows: OffsetRows::None,
- })
- );
+ }),
+ limit_by: vec![],
+ };
+ let ast = dialects.verified_query("SELECT 'foo' OFFSET 2");
+ assert_eq!(ast.limit_clause, Some(expected_limit_clause));
}
#[test]
@@ -8273,13 +8315,15 @@ fn parse_fetch() {
let ast = verified_query(
"SELECT foo FROM bar WHERE foo = 4 ORDER BY baz OFFSET 2 ROWS FETCH
FIRST 2 ROWS ONLY",
);
- assert_eq!(
- ast.offset,
- Some(Offset {
+ let expected_limit_clause = Some(LimitClause::LimitOffset {
+ limit: None,
+ offset: Some(Offset {
value: Expr::value(number("2")),
rows: OffsetRows::Rows,
- })
- );
+ }),
+ limit_by: vec![],
+ });
+ assert_eq!(ast.limit_clause, expected_limit_clause);
assert_eq!(ast.fetch, fetch_first_two_rows_only);
let ast = verified_query(
"SELECT foo FROM (SELECT * FROM bar FETCH FIRST 2 ROWS ONLY) FETCH
FIRST 2 ROWS ONLY",
@@ -8295,24 +8339,20 @@ fn parse_fetch() {
_ => panic!("Test broke"),
}
let ast = verified_query("SELECT foo FROM (SELECT * FROM bar OFFSET 2 ROWS
FETCH FIRST 2 ROWS ONLY) OFFSET 2 ROWS FETCH FIRST 2 ROWS ONLY");
- assert_eq!(
- ast.offset,
- Some(Offset {
+ let expected_limit_clause = &Some(LimitClause::LimitOffset {
+ limit: None,
+ offset: Some(Offset {
value: Expr::value(number("2")),
rows: OffsetRows::Rows,
- })
- );
+ }),
+ limit_by: vec![],
+ });
+ assert_eq!(&ast.limit_clause, expected_limit_clause);
assert_eq!(ast.fetch, fetch_first_two_rows_only);
match *ast.body {
SetExpr::Select(s) => match only(s.from).relation {
TableFactor::Derived { subquery, .. } => {
- assert_eq!(
- subquery.offset,
- Some(Offset {
- value: Expr::value(number("2")),
- rows: OffsetRows::Rows,
- })
- );
+ assert_eq!(&subquery.limit_clause, expected_limit_clause);
assert_eq!(subquery.fetch, fetch_first_two_rows_only);
}
_ => panic!("Test broke"),
@@ -9358,9 +9398,7 @@ fn parse_merge() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -9678,21 +9716,18 @@ fn test_placeholder() {
})
);
- let sql = "SELECT * FROM student LIMIT $1 OFFSET $2";
- let ast = dialects.verified_query(sql);
- assert_eq!(
- ast.limit,
- Some(Expr::Value(
- (Value::Placeholder("$1".into())).with_empty_span()
- ))
- );
- assert_eq!(
- ast.offset,
- Some(Offset {
+ let ast = dialects.verified_query("SELECT * FROM student LIMIT $1 OFFSET
$2");
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: Some(Expr::Value(
+ (Value::Placeholder("$1".into())).with_empty_span(),
+ )),
+ offset: Some(Offset {
value:
Expr::Value((Value::Placeholder("$2".into())).with_empty_span()),
rows: OffsetRows::None,
}),
- );
+ limit_by: vec![],
+ };
+ assert_eq!(ast.limit_clause, Some(expected_limit_clause));
let dialects = TestedDialects::new(vec![
Box::new(GenericDialect {}),
@@ -9772,40 +9807,34 @@ fn verified_expr(query: &str) -> Expr {
#[test]
fn parse_offset_and_limit() {
let sql = "SELECT foo FROM bar LIMIT 1 OFFSET 2";
- let expect = Some(Offset {
- value: Expr::value(number("2")),
- rows: OffsetRows::None,
+ let expected_limit_clause = Some(LimitClause::LimitOffset {
+ limit: Some(Expr::value(number("1"))),
+ offset: Some(Offset {
+ value: Expr::value(number("2")),
+ rows: OffsetRows::None,
+ }),
+ limit_by: vec![],
});
let ast = verified_query(sql);
- assert_eq!(ast.offset, expect);
- assert_eq!(ast.limit, Some(Expr::value(number("1"))));
+ assert_eq!(ast.limit_clause, expected_limit_clause);
// different order is OK
one_statement_parses_to("SELECT foo FROM bar OFFSET 2 LIMIT 1", sql);
// mysql syntax is ok for some dialects
- TestedDialects::new(vec![
- Box::new(GenericDialect {}),
- Box::new(MySqlDialect {}),
- Box::new(SQLiteDialect {}),
- Box::new(ClickHouseDialect {}),
- ])
- .one_statement_parses_to("SELECT foo FROM bar LIMIT 2, 1", sql);
+ all_dialects_where(|d| d.supports_limit_comma())
+ .verified_query("SELECT foo FROM bar LIMIT 2, 1");
// expressions are allowed
let sql = "SELECT foo FROM bar LIMIT 1 + 2 OFFSET 3 * 4";
let ast = verified_query(sql);
- assert_eq!(
- ast.limit,
- Some(Expr::BinaryOp {
+ let expected_limit_clause = LimitClause::LimitOffset {
+ limit: Some(Expr::BinaryOp {
left: Box::new(Expr::value(number("1"))),
op: BinaryOperator::Plus,
right: Box::new(Expr::value(number("2"))),
}),
- );
- assert_eq!(
- ast.offset,
- Some(Offset {
+ offset: Some(Offset {
value: Expr::BinaryOp {
left: Box::new(Expr::value(number("3"))),
op: BinaryOperator::Multiply,
@@ -9813,7 +9842,12 @@ fn parse_offset_and_limit() {
},
rows: OffsetRows::None,
}),
- );
+ limit_by: vec![],
+ };
+ assert_eq!(ast.limit_clause, Some(expected_limit_clause),);
+
+ // OFFSET without LIMIT
+ verified_stmt("SELECT foo FROM bar OFFSET 2");
// Can't repeat OFFSET / LIMIT
let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 OFFSET 2");
@@ -11227,9 +11261,7 @@ fn parse_unload() {
flavor: SelectFlavor::Standard,
}))),
with: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -12400,9 +12432,7 @@ fn test_extract_seconds_ok() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -14265,11 +14295,9 @@ fn test_select_from_first() {
flavor,
}))),
order_by: None,
- limit: None,
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
- limit_by: vec![],
for_clause: None,
settings: None,
format_clause: None,
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 386bd178..af71d252 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -107,9 +107,7 @@ fn parse_create_procedure() {
or_alter: true,
body: vec![Statement::Query(Box::new(Query {
with: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1161,9 +1159,7 @@ fn parse_substring_in_select() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1264,9 +1260,7 @@ fn parse_mssql_declare() {
}),
Statement::Query(Box::new(Query {
with: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index 13a8a6cc..a5633593 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -1107,9 +1107,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1161,9 +1159,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1209,9 +1205,7 @@ fn parse_escaped_backticks_with_escape() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1261,9 +1255,7 @@ fn parse_escaped_backticks_with_no_escape() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1438,9 +1430,7 @@ fn parse_simple_insert() {
]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1488,9 +1478,7 @@ fn parse_ignore_insert() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1538,9 +1526,7 @@ fn parse_priority_insert() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1585,9 +1571,7 @@ fn parse_priority_insert() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1634,9 +1618,7 @@ fn parse_insert_as() {
)]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1698,9 +1680,7 @@ fn parse_insert_as() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1749,9 +1729,7 @@ fn parse_replace_insert() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1791,9 +1769,7 @@ fn parse_empty_row_insert() {
rows: vec![vec![], vec![]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -1857,9 +1833,7 @@ fn parse_insert_with_on_duplicate_update() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -2596,9 +2570,7 @@ fn parse_substring_in_select() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -2737,10 +2709,8 @@ fn parse_set_names() {
#[test]
fn parse_limit_my_sql_syntax() {
- mysql_and_generic().one_statement_parses_to(
- "SELECT id, fname, lname FROM customer LIMIT 5, 10",
- "SELECT id, fname, lname FROM customer LIMIT 10 OFFSET 5",
- );
+ mysql_and_generic().verified_stmt("SELECT id, fname, lname FROM customer
LIMIT 10 OFFSET 5");
+ mysql_and_generic().verified_stmt("SELECT id, fname, lname FROM customer
LIMIT 5, 10");
mysql_and_generic().verified_stmt("SELECT * FROM user LIMIT ? OFFSET ?");
}
@@ -2903,9 +2873,7 @@ fn parse_hex_string_introducer() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index a65c4fa3..1a98870f 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -1319,9 +1319,7 @@ fn parse_copy_to() {
flavor: SelectFlavor::Standard,
}))),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -2955,9 +2953,7 @@ fn parse_array_subquery_expr() {
}))),
}),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -4747,9 +4743,7 @@ fn test_simple_postgres_insert_with_alias() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -4820,9 +4814,7 @@ fn test_simple_postgres_insert_with_alias() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
@@ -4891,9 +4883,7 @@ fn test_simple_insert_with_quoted_alias() {
]]
})),
order_by: None,
- limit: None,
- limit_by: vec![],
- offset: None,
+ limit_clause: None,
fetch: None,
locks: vec![],
for_clause: None,
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]