-
Notifications
You must be signed in to change notification settings - Fork 691
MySQL: Add support for SELECT modifiers
#2172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -334,6 +334,70 @@ pub enum SelectFlavor { | |||||
| FromFirstNoSelect, | ||||||
| } | ||||||
|
|
||||||
| /// MySQL-specific SELECT modifiers that appear after the SELECT keyword. | ||||||
| /// | ||||||
| /// These modifiers affect query execution and optimization. They can appear | ||||||
| /// in any order after SELECT and before the column list, and can be | ||||||
| /// interleaved with DISTINCT/DISTINCTROW/ALL: | ||||||
| /// | ||||||
| /// ```sql | ||||||
| /// SELECT | ||||||
| /// [ALL | DISTINCT | DISTINCTROW] | ||||||
| /// [HIGH_PRIORITY] | ||||||
| /// [STRAIGHT_JOIN] | ||||||
| /// [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT] | ||||||
| /// [SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] | ||||||
| /// select_expr [, select_expr] ... | ||||||
| /// ``` | ||||||
| /// | ||||||
| /// See [MySQL SELECT](https://dev.mysql.com/doc/refman/8.4/en/select.html). | ||||||
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Default)] | ||||||
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | ||||||
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | ||||||
| pub struct SelectModifiers { | ||||||
| /// `HIGH_PRIORITY` gives the SELECT higher priority than statements that update a table. | ||||||
| pub high_priority: bool, | ||||||
| /// `STRAIGHT_JOIN` forces the optimizer to join tables in the order listed in the FROM clause. | ||||||
| pub straight_join: bool, | ||||||
| /// `SQL_SMALL_RESULT` hints that the result set is small, using in-memory temp tables. | ||||||
| pub sql_small_result: bool, | ||||||
| /// `SQL_BIG_RESULT` hints that the result set is large, using disk-based temp tables. | ||||||
| pub sql_big_result: bool, | ||||||
| /// `SQL_BUFFER_RESULT` forces the result to be put into a temporary table to release locks early. | ||||||
| pub sql_buffer_result: bool, | ||||||
| /// `SQL_NO_CACHE` tells MySQL not to cache the query result. | ||||||
| pub sql_no_cache: bool, | ||||||
| /// `SQL_CALC_FOUND_ROWS` tells MySQL to calculate the total number of rows. | ||||||
| pub sql_calc_found_rows: bool, | ||||||
| } | ||||||
|
|
||||||
| impl fmt::Display for SelectModifiers { | ||||||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
| if self.high_priority { | ||||||
| f.write_str(" HIGH_PRIORITY")?; | ||||||
| } | ||||||
| if self.straight_join { | ||||||
| f.write_str(" STRAIGHT_JOIN")?; | ||||||
| } | ||||||
| if self.sql_small_result { | ||||||
| f.write_str(" SQL_SMALL_RESULT")?; | ||||||
| } | ||||||
| if self.sql_big_result { | ||||||
| f.write_str(" SQL_BIG_RESULT")?; | ||||||
| } | ||||||
| if self.sql_buffer_result { | ||||||
| f.write_str(" SQL_BUFFER_RESULT")?; | ||||||
| } | ||||||
| if self.sql_no_cache { | ||||||
| f.write_str(" SQL_NO_CACHE")?; | ||||||
| } | ||||||
| if self.sql_calc_found_rows { | ||||||
| f.write_str(" SQL_CALC_FOUND_ROWS")?; | ||||||
| } | ||||||
| Ok(()) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// A restricted variant of `SELECT` (without CTEs/`ORDER BY`), which may | ||||||
| /// appear either as the only body item of a `Query`, or as an operand | ||||||
| /// to a set operation like `UNION`. | ||||||
|
|
@@ -345,6 +409,10 @@ pub struct Select { | |||||
| pub select_token: AttachedToken, | ||||||
| /// `SELECT [DISTINCT] ...` | ||||||
| pub distinct: Option<Distinct>, | ||||||
| /// MySQL-specific SELECT modifiers. | ||||||
| /// | ||||||
| /// See [MySQL SELECT](https://dev.mysql.com/doc/refman/8.4/en/select.html). | ||||||
| pub select_modifiers: SelectModifiers, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| /// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]` | ||||||
| pub top: Option<Top>, | ||||||
| /// Whether the top was located before `ALL`/`DISTINCT` | ||||||
|
|
@@ -415,6 +483,8 @@ impl fmt::Display for Select { | |||||
| value_table_mode.fmt(f)?; | ||||||
| } | ||||||
|
|
||||||
| self.select_modifiers.fmt(f)?; | ||||||
|
|
||||||
| if let Some(ref top) = self.top { | ||||||
| if self.top_before_distinct { | ||||||
| f.write_str(" ")?; | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4906,14 +4906,17 @@ impl<'a> Parser<'a> { | |||||||||
| /// Parse either `ALL`, `DISTINCT` or `DISTINCT ON (...)`. Returns [`None`] if `ALL` is parsed | ||||||||||
| /// and results in a [`ParserError`] if both `ALL` and `DISTINCT` are found. | ||||||||||
| pub fn parse_all_or_distinct(&mut self) -> Result<Option<Distinct>, ParserError> { | ||||||||||
| let loc = self.peek_token().span.start; | ||||||||||
| let all = self.parse_keyword(Keyword::ALL); | ||||||||||
| let distinct = self.parse_keyword(Keyword::DISTINCT); | ||||||||||
| if !distinct { | ||||||||||
| return Ok(None); | ||||||||||
| } | ||||||||||
| if all { | ||||||||||
| return parser_err!("Cannot specify both ALL and DISTINCT".to_string(), loc); | ||||||||||
| self.prev_token(); | ||||||||||
| return self.expected( | ||||||||||
| "ALL alone without DISTINCT or DISTINCTROW", | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this diff seems incorrect to mention |
||||||||||
| self.peek_token(), | ||||||||||
| ); | ||||||||||
| } | ||||||||||
| let on = self.parse_keyword(Keyword::ON); | ||||||||||
| if !on { | ||||||||||
|
|
@@ -13823,6 +13826,7 @@ impl<'a> Parser<'a> { | |||||||||
| return Ok(Select { | ||||||||||
| select_token: AttachedToken(from_token), | ||||||||||
| distinct: None, | ||||||||||
| select_modifiers: SelectModifiers::default(), | ||||||||||
| top: None, | ||||||||||
| top_before_distinct: false, | ||||||||||
| projection: vec![], | ||||||||||
|
|
@@ -13851,13 +13855,26 @@ impl<'a> Parser<'a> { | |||||||||
| let select_token = self.expect_keyword(Keyword::SELECT)?; | ||||||||||
| let value_table_mode = self.parse_value_table_mode()?; | ||||||||||
|
|
||||||||||
| let (select_modifiers, distinct_select_modifier) = | ||||||||||
| if self.dialect.supports_select_modifiers() { | ||||||||||
| self.parse_select_modifiers()? | ||||||||||
| } else { | ||||||||||
| (SelectModifiers::default(), None) | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| let mut top_before_distinct = false; | ||||||||||
| let mut top = None; | ||||||||||
| if self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) { | ||||||||||
| top = Some(self.parse_top()?); | ||||||||||
| top_before_distinct = true; | ||||||||||
| } | ||||||||||
| let distinct = self.parse_all_or_distinct()?; | ||||||||||
|
|
||||||||||
| let distinct = if distinct_select_modifier.is_some() { | ||||||||||
| distinct_select_modifier | ||||||||||
| } else { | ||||||||||
| self.parse_all_or_distinct()? | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| if !self.dialect.supports_top_before_distinct() && self.parse_keyword(Keyword::TOP) { | ||||||||||
| top = Some(self.parse_top()?); | ||||||||||
| } | ||||||||||
|
|
@@ -14005,6 +14022,7 @@ impl<'a> Parser<'a> { | |||||||||
| Ok(Select { | ||||||||||
| select_token: AttachedToken(select_token), | ||||||||||
| distinct, | ||||||||||
| select_modifiers, | ||||||||||
| top, | ||||||||||
| top_before_distinct, | ||||||||||
| projection, | ||||||||||
|
|
@@ -14032,6 +14050,79 @@ impl<'a> Parser<'a> { | |||||||||
| }) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /// Parses SELECT modifiers and DISTINCT/ALL in any order. Allows HIGH_PRIORITY, STRAIGHT_JOIN, | ||||||||||
| /// SQL_SMALL_RESULT, SQL_BIG_RESULT, SQL_BUFFER_RESULT, SQL_NO_CACHE, SQL_CALC_FOUND_ROWS and | ||||||||||
| /// DISTINCT/DISTINCTROW/ALL to appear in any order. | ||||||||||
|
Comment on lines
+14053
to
+14055
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Thinking we can avoid enumerating the variants so that they don't go out of sync with the impl |
||||||||||
| fn parse_select_modifiers( | ||||||||||
| &mut self, | ||||||||||
| ) -> Result<(SelectModifiers, Option<Distinct>), ParserError> { | ||||||||||
| let mut modifiers = SelectModifiers::default(); | ||||||||||
| let mut distinct: Option<Distinct> = None; | ||||||||||
| let mut has_all = false; | ||||||||||
|
Comment on lines
+14060
to
+14061
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we instead introduce a proper enum to represent the three states? the current impl I think is harder to follow which combinations are valid and if/when the specified option is ignored in the output. |
||||||||||
|
|
||||||||||
| let keywords = &[ | ||||||||||
| Keyword::ALL, | ||||||||||
| Keyword::DISTINCT, | ||||||||||
| Keyword::DISTINCTROW, | ||||||||||
| Keyword::HIGH_PRIORITY, | ||||||||||
| Keyword::STRAIGHT_JOIN, | ||||||||||
| Keyword::SQL_SMALL_RESULT, | ||||||||||
| Keyword::SQL_BIG_RESULT, | ||||||||||
| Keyword::SQL_BUFFER_RESULT, | ||||||||||
| Keyword::SQL_NO_CACHE, | ||||||||||
| Keyword::SQL_CALC_FOUND_ROWS, | ||||||||||
| ]; | ||||||||||
|
|
||||||||||
| while let Some(keyword) = self.parse_one_of_keywords(keywords) { | ||||||||||
| match keyword { | ||||||||||
| Keyword::ALL => { | ||||||||||
| if has_all { | ||||||||||
| self.prev_token(); | ||||||||||
| return self.expected("SELECT without duplicate ALL", self.peek_token()); | ||||||||||
| } | ||||||||||
| if distinct.is_some() { | ||||||||||
| self.prev_token(); | ||||||||||
| return self.expected("DISTINCT alone without ALL", self.peek_token()); | ||||||||||
| } | ||||||||||
| has_all = true; | ||||||||||
| } | ||||||||||
| Keyword::DISTINCT | Keyword::DISTINCTROW => { | ||||||||||
| if distinct.is_some() { | ||||||||||
| self.prev_token(); | ||||||||||
| return self.expected( | ||||||||||
| "SELECT without duplicate DISTINCT or DISTINCTROW", | ||||||||||
| self.peek_token(), | ||||||||||
| ); | ||||||||||
| } | ||||||||||
| if has_all { | ||||||||||
| self.prev_token(); | ||||||||||
| return self.expected( | ||||||||||
| "ALL alone without DISTINCT or DISTINCTROW", | ||||||||||
| self.peek_token(), | ||||||||||
| ); | ||||||||||
| } | ||||||||||
| distinct = Some(Distinct::Distinct); | ||||||||||
| } | ||||||||||
| Keyword::HIGH_PRIORITY => modifiers.high_priority = true, | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
similar for the others is something like this needed if it's invalid that they occur multiple times (can we add test cases for the behavior)? |
||||||||||
| Keyword::STRAIGHT_JOIN => modifiers.straight_join = true, | ||||||||||
| Keyword::SQL_SMALL_RESULT => modifiers.sql_small_result = true, | ||||||||||
| Keyword::SQL_BIG_RESULT => modifiers.sql_big_result = true, | ||||||||||
| Keyword::SQL_BUFFER_RESULT => modifiers.sql_buffer_result = true, | ||||||||||
| Keyword::SQL_NO_CACHE => modifiers.sql_no_cache = true, | ||||||||||
| Keyword::SQL_CALC_FOUND_ROWS => modifiers.sql_calc_found_rows = true, | ||||||||||
| _ => { | ||||||||||
| self.prev_token(); | ||||||||||
| return self.expected( | ||||||||||
| "HIGH_PRIORITY, STRAIGHT_JOIN, or other MySQL select modifier", | ||||||||||
| self.peek_token(), | ||||||||||
| ); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| Ok((modifiers, distinct)) | ||||||||||
| } | ||||||||||
|
|
||||||||||
| fn parse_value_table_mode(&mut self) -> Result<Option<ValueTableMode>, ParserError> { | ||||||||||
| if !dialect_of!(self is BigQueryDialect) { | ||||||||||
| return Ok(None); | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we include the mysql links individually on the fields as well? that way if other dialects add their own variants in the future the doc would be consistent