Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,15 @@ pub use self::query::{
OffsetRows, OpenJsonTableColumn, OrderBy, OrderByExpr, OrderByKind, OrderByOptions,
PipeOperator, 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, XmlNamespaceDefinition,
XmlPassingArgument, XmlPassingClause, XmlTableColumn, XmlTableColumnOption,
SelectFlavor, SelectInto, SelectItem, SelectItemQualifiedWildcardKind, SelectModifiers,
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,
XmlNamespaceDefinition, XmlPassingArgument, XmlPassingClause, XmlTableColumn,
XmlTableColumnOption,
};

pub use self::trigger::{
Expand Down
70 changes: 70 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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,
/// MSSQL syntax: `TOP (<N>) [ PERCENT ] [ WITH TIES ]`
pub top: Option<Top>,
/// Whether the top was located before `ALL`/`DISTINCT`
Expand Down Expand Up @@ -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(" ")?;
Expand Down
5 changes: 3 additions & 2 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2230,7 +2230,8 @@ impl Spanned for Select {
let Select {
select_token,
distinct: _, // todo
top: _, // todo, mysql specific
select_modifiers: _,
top: _, // todo, mysql specific
projection,
exclude: _,
into,
Expand Down Expand Up @@ -2801,7 +2802,7 @@ WHERE id = 1
UPDATE SET target_table.description = source_table.description

WHEN MATCHED AND target_table.x != 'X' THEN DELETE
WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW
"#;

let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
Expand Down
13 changes: 13 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,19 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports MySQL-specific SELECT modifiers
/// like `HIGH_PRIORITY`, `STRAIGHT_JOIN`, `SQL_SMALL_RESULT`, etc.
///
/// For example:
/// ```sql
/// SELECT HIGH_PRIORITY STRAIGHT_JOIN SQL_SMALL_RESULT * FROM t1 JOIN t2 ON ...
/// ```
///
/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/select.html)
fn supports_select_modifiers(&self) -> bool {
false
}

/// Dialect-specific infix parser override
///
/// This method is called to parse the next infix expression.
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ impl Dialect for MySqlDialect {
true
}

fn supports_select_modifiers(&self) -> bool {
true
}

fn supports_set_names(&self) -> bool {
true
}
Expand Down
6 changes: 6 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ define_keywords!(
DISCARD,
DISCONNECT,
DISTINCT,
DISTINCTROW,
DISTRIBUTE,
DIV,
DO,
Expand Down Expand Up @@ -956,6 +957,11 @@ define_keywords!(
SQLEXCEPTION,
SQLSTATE,
SQLWARNING,
SQL_BIG_RESULT,
SQL_BUFFER_RESULT,
SQL_CALC_FOUND_ROWS,
SQL_NO_CACHE,
SQL_SMALL_RESULT,
SQRT,
SRID,
STABLE,
Expand Down
97 changes: 94 additions & 3 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
self.peek_token(),
);
}
let on = self.parse_keyword(Keyword::ON);
if !on {
Expand Down Expand Up @@ -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![],
Expand Down Expand Up @@ -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()?);
}
Expand Down Expand Up @@ -14005,6 +14022,7 @@ impl<'a> Parser<'a> {
Ok(Select {
select_token: AttachedToken(select_token),
distinct,
select_modifiers,
top,
top_before_distinct,
projection,
Expand Down Expand Up @@ -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.
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;

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,
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);
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2682,6 +2682,7 @@ fn test_export_data() {
Span::empty()
)),
distinct: None,
select_modifiers: SelectModifiers::default(),
top: None,
top_before_distinct: false,
projection: vec![
Expand Down Expand Up @@ -2786,6 +2787,7 @@ fn test_export_data() {
Span::empty()
)),
distinct: None,
select_modifiers: SelectModifiers::default(),
top: None,
top_before_distinct: false,
projection: vec![
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fn parse_map_access_expr() {
assert_eq!(
Select {
distinct: None,
select_modifiers: SelectModifiers::default(),
select_token: AttachedToken::empty(),
top: None,
top_before_distinct: false,
Expand Down
Loading