Skip to content

Commit f28032e

Browse files
AvivDavid-SatoriVedin
authored andcommitted
Add support for mysql table hints (apache#1675)
1 parent b16de30 commit f28032e

12 files changed

+266
-5
lines changed

src/ast/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ pub use self::query::{
6969
OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem,
7070
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select,
7171
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table,
72-
TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs, TableSample,
73-
TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
74-
TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion,
75-
TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode, Values,
76-
WildcardAdditionalOptions, With, WithFill,
72+
TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs, TableIndexHintForClause,
73+
TableIndexHintType, TableIndexHints, TableIndexType, TableSample, TableSampleBucket,
74+
TableSampleKind, TableSampleMethod, TableSampleModifier, TableSampleQuantity, TableSampleSeed,
75+
TableSampleSeedModifier, TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity,
76+
UpdateTableFromKind, ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill,
7777
};
7878

7979
pub use self::trigger::{

src/ast/query.rs

+82
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,81 @@ pub struct TableFunctionArgs {
975975
pub settings: Option<Vec<Setting>>,
976976
}
977977

978+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
979+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
980+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
981+
pub enum TableIndexHintType {
982+
Use,
983+
Ignore,
984+
Force,
985+
}
986+
987+
impl fmt::Display for TableIndexHintType {
988+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
989+
f.write_str(match self {
990+
TableIndexHintType::Use => "USE",
991+
TableIndexHintType::Ignore => "IGNORE",
992+
TableIndexHintType::Force => "FORCE",
993+
})
994+
}
995+
}
996+
997+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
998+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
999+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1000+
pub enum TableIndexType {
1001+
Index,
1002+
Key,
1003+
}
1004+
1005+
impl fmt::Display for TableIndexType {
1006+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1007+
f.write_str(match self {
1008+
TableIndexType::Index => "INDEX",
1009+
TableIndexType::Key => "KEY",
1010+
})
1011+
}
1012+
}
1013+
1014+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1015+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1016+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1017+
pub enum TableIndexHintForClause {
1018+
Join,
1019+
OrderBy,
1020+
GroupBy,
1021+
}
1022+
1023+
impl fmt::Display for TableIndexHintForClause {
1024+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1025+
f.write_str(match self {
1026+
TableIndexHintForClause::Join => "JOIN",
1027+
TableIndexHintForClause::OrderBy => "ORDER BY",
1028+
TableIndexHintForClause::GroupBy => "GROUP BY",
1029+
})
1030+
}
1031+
}
1032+
1033+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1034+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1035+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1036+
pub struct TableIndexHints {
1037+
pub hint_type: TableIndexHintType,
1038+
pub index_type: TableIndexType,
1039+
pub for_clause: Option<TableIndexHintForClause>,
1040+
pub index_names: Vec<Ident>,
1041+
}
1042+
1043+
impl fmt::Display for TableIndexHints {
1044+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1045+
write!(f, "{} {} ", self.hint_type, self.index_type)?;
1046+
if let Some(for_clause) = &self.for_clause {
1047+
write!(f, "FOR {} ", for_clause)?;
1048+
}
1049+
write!(f, "({})", display_comma_separated(&self.index_names))
1050+
}
1051+
}
1052+
9781053
/// A table name or a parenthesized subquery with an optional alias
9791054
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
9801055
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -1009,6 +1084,9 @@ pub enum TableFactor {
10091084
/// Optional table sample modifier
10101085
/// See: <https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#sample-clause>
10111086
sample: Option<TableSampleKind>,
1087+
/// Optional index hints(mysql)
1088+
/// See: <https://dev.mysql.com/doc/refman/8.4/en/index-hints.html>
1089+
index_hints: Vec<TableIndexHints>,
10121090
},
10131091
Derived {
10141092
lateral: bool,
@@ -1590,6 +1668,7 @@ impl fmt::Display for TableFactor {
15901668
with_ordinality,
15911669
json_path,
15921670
sample,
1671+
index_hints,
15931672
} => {
15941673
write!(f, "{name}")?;
15951674
if let Some(json_path) = json_path {
@@ -1618,6 +1697,9 @@ impl fmt::Display for TableFactor {
16181697
if let Some(alias) = alias {
16191698
write!(f, " AS {alias}")?;
16201699
}
1700+
if !index_hints.is_empty() {
1701+
write!(f, " {}", display_separated(index_hints, " "))?;
1702+
}
16211703
if !with_hints.is_empty() {
16221704
write!(f, " WITH ({})", display_comma_separated(with_hints))?;
16231705
}

src/ast/spans.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,7 @@ impl Spanned for TableFactor {
17661766
partitions: _,
17671767
json_path: _,
17681768
sample: _,
1769+
index_hints: _,
17691770
} => union_spans(
17701771
name.0
17711772
.iter()

src/dialect/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,10 @@ pub trait Dialect: Debug + Any {
854854
fn supports_string_escape_constant(&self) -> bool {
855855
false
856856
}
857+
/// Returns true if the dialect supports the table hints in the `FROM` clause.
858+
fn supports_table_hints(&self) -> bool {
859+
false
860+
}
857861
}
858862

859863
/// This represents the operators for which precedence must be defined

src/dialect/mysql.rs

+14
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ use crate::{
2525
parser::{Parser, ParserError},
2626
};
2727

28+
use super::keywords;
29+
30+
const RESERVED_FOR_TABLE_ALIAS_MYSQL: &[Keyword] = &[Keyword::USE, Keyword::IGNORE, Keyword::FORCE];
31+
2832
/// A [`Dialect`] for [MySQL](https://www.mysql.com/)
2933
#[derive(Debug)]
3034
pub struct MySqlDialect {}
@@ -111,6 +115,16 @@ impl Dialect for MySqlDialect {
111115
fn supports_user_host_grantee(&self) -> bool {
112116
true
113117
}
118+
119+
fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool {
120+
explicit
121+
|| (!keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
122+
&& !RESERVED_FOR_TABLE_ALIAS_MYSQL.contains(kw))
123+
}
124+
125+
fn supports_table_hints(&self) -> bool {
126+
true
127+
}
114128
}
115129

116130
/// `LOCK TABLES`

src/parser/mod.rs

+67
Original file line numberDiff line numberDiff line change
@@ -8910,6 +8910,64 @@ impl<'a> Parser<'a> {
89108910
}
89118911
}
89128912

8913+
fn parse_table_index_hints(&mut self) -> Result<Vec<TableIndexHints>, ParserError> {
8914+
let mut hints = vec![];
8915+
while let Some(hint_type) =
8916+
self.parse_one_of_keywords(&[Keyword::USE, Keyword::IGNORE, Keyword::FORCE])
8917+
{
8918+
let hint_type = match hint_type {
8919+
Keyword::USE => TableIndexHintType::Use,
8920+
Keyword::IGNORE => TableIndexHintType::Ignore,
8921+
Keyword::FORCE => TableIndexHintType::Force,
8922+
_ => {
8923+
return self.expected(
8924+
"expected to match USE/IGNORE/FORCE keyword",
8925+
self.peek_token(),
8926+
)
8927+
}
8928+
};
8929+
let index_type = match self.parse_one_of_keywords(&[Keyword::INDEX, Keyword::KEY]) {
8930+
Some(Keyword::INDEX) => TableIndexType::Index,
8931+
Some(Keyword::KEY) => TableIndexType::Key,
8932+
_ => {
8933+
return self.expected("expected to match INDEX/KEY keyword", self.peek_token())
8934+
}
8935+
};
8936+
let for_clause = if self.parse_keyword(Keyword::FOR) {
8937+
let clause = if self.parse_keyword(Keyword::JOIN) {
8938+
TableIndexHintForClause::Join
8939+
} else if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
8940+
TableIndexHintForClause::OrderBy
8941+
} else if self.parse_keywords(&[Keyword::GROUP, Keyword::BY]) {
8942+
TableIndexHintForClause::GroupBy
8943+
} else {
8944+
return self.expected(
8945+
"expected to match FOR/ORDER BY/GROUP BY table hint in for clause",
8946+
self.peek_token(),
8947+
);
8948+
};
8949+
Some(clause)
8950+
} else {
8951+
None
8952+
};
8953+
8954+
self.expect_token(&Token::LParen)?;
8955+
let index_names = if self.peek_token().token != Token::RParen {
8956+
self.parse_comma_separated(Parser::parse_identifier)?
8957+
} else {
8958+
vec![]
8959+
};
8960+
self.expect_token(&Token::RParen)?;
8961+
hints.push(TableIndexHints {
8962+
hint_type,
8963+
index_type,
8964+
for_clause,
8965+
index_names,
8966+
});
8967+
}
8968+
Ok(hints)
8969+
}
8970+
89138971
/// Wrapper for parse_optional_alias_inner, left for backwards-compatibility
89148972
/// but new flows should use the context-specific methods such as `maybe_parse_select_item_alias`
89158973
/// and `maybe_parse_table_alias`.
@@ -11257,6 +11315,14 @@ impl<'a> Parser<'a> {
1125711315

1125811316
let alias = self.maybe_parse_table_alias()?;
1125911317

11318+
// MYSQL-specific table hints:
11319+
let index_hints = if self.dialect.supports_table_hints() {
11320+
self.maybe_parse(|p| p.parse_table_index_hints())?
11321+
.unwrap_or(vec![])
11322+
} else {
11323+
vec![]
11324+
};
11325+
1126011326
// MSSQL-specific table hints:
1126111327
let mut with_hints = vec![];
1126211328
if self.parse_keyword(Keyword::WITH) {
@@ -11285,6 +11351,7 @@ impl<'a> Parser<'a> {
1128511351
with_ordinality,
1128611352
json_path,
1128711353
sample,
11354+
index_hints,
1128811355
};
1128911356

1129011357
while let Some(kw) = self.parse_one_of_keywords(&[Keyword::PIVOT, Keyword::UNPIVOT]) {

src/test_utils.rs

+3
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ pub fn table(name: impl Into<String>) -> TableFactor {
362362
with_ordinality: false,
363363
json_path: None,
364364
sample: None,
365+
index_hints: vec![],
365366
}
366367
}
367368

@@ -376,6 +377,7 @@ pub fn table_from_name(name: ObjectName) -> TableFactor {
376377
with_ordinality: false,
377378
json_path: None,
378379
sample: None,
380+
index_hints: vec![],
379381
}
380382
}
381383

@@ -393,6 +395,7 @@ pub fn table_with_alias(name: impl Into<String>, alias: impl Into<String>) -> Ta
393395
with_ordinality: false,
394396
json_path: None,
395397
sample: None,
398+
index_hints: vec![],
396399
}
397400
}
398401

tests/sqlparser_bigquery.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,7 @@ fn parse_table_time_travel() {
15651565
with_ordinality: false,
15661566
json_path: None,
15671567
sample: None,
1568+
index_hints: vec![],
15681569
},
15691570
joins: vec![]
15701571
},]
@@ -1665,6 +1666,7 @@ fn parse_merge() {
16651666
with_ordinality: false,
16661667
json_path: None,
16671668
sample: None,
1669+
index_hints: vec![],
16681670
},
16691671
table
16701672
);
@@ -1682,6 +1684,7 @@ fn parse_merge() {
16821684
with_ordinality: false,
16831685
json_path: None,
16841686
sample: None,
1687+
index_hints: vec![],
16851688
},
16861689
source
16871690
);

0 commit comments

Comments
 (0)