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 f7b0812b Add support for mysql table hints (#1675)
f7b0812b is described below
commit f7b0812b01111c678c595f6f79b8d2f5cf5cb305
Author: AvivDavid-Satori <[email protected]>
AuthorDate: Tue Jan 28 10:41:03 2025 +0200
Add support for mysql table hints (#1675)
---
src/ast/mod.rs | 10 +++---
src/ast/query.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++
src/ast/spans.rs | 1 +
src/dialect/mod.rs | 4 +++
src/dialect/mysql.rs | 14 ++++++++
src/parser/mod.rs | 67 ++++++++++++++++++++++++++++++++++++
src/test_utils.rs | 3 ++
tests/sqlparser_bigquery.rs | 3 ++
tests/sqlparser_common.rs | 78 ++++++++++++++++++++++++++++++++++++++++++
tests/sqlparser_hive.rs | 1 +
tests/sqlparser_mssql.rs | 6 ++++
tests/sqlparser_mysql.rs | 2 ++
12 files changed, 266 insertions(+), 5 deletions(-)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index e64b7d3d..6917b7c9 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -69,11 +69,11 @@ pub use self::query::{
OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query,
RenameSelectItem,
RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem,
RowsPerMatch, Select,
SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting,
SymbolDefinition, Table,
- TableAlias, TableAliasColumnDef, TableFactor, TableFunctionArgs,
TableSample,
- TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier,
- TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier,
TableSampleUnit, TableVersion,
- TableWithJoins, Top, TopQuantity, UpdateTableFromKind, ValueTableMode,
Values,
- WildcardAdditionalOptions, With, WithFill,
+ 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 e982c7f0..09058f76 100644
--- a/src/ast/query.rs
+++ b/src/ast/query.rs
@@ -975,6 +975,81 @@ pub struct TableFunctionArgs {
pub settings: Option<Vec<Setting>>,
}
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum TableIndexHintType {
+ Use,
+ Ignore,
+ Force,
+}
+
+impl fmt::Display for TableIndexHintType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(match self {
+ TableIndexHintType::Use => "USE",
+ TableIndexHintType::Ignore => "IGNORE",
+ TableIndexHintType::Force => "FORCE",
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum TableIndexType {
+ Index,
+ Key,
+}
+
+impl fmt::Display for TableIndexType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(match self {
+ TableIndexType::Index => "INDEX",
+ TableIndexType::Key => "KEY",
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum TableIndexHintForClause {
+ Join,
+ OrderBy,
+ GroupBy,
+}
+
+impl fmt::Display for TableIndexHintForClause {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(match self {
+ TableIndexHintForClause::Join => "JOIN",
+ TableIndexHintForClause::OrderBy => "ORDER BY",
+ TableIndexHintForClause::GroupBy => "GROUP BY",
+ })
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct TableIndexHints {
+ pub hint_type: TableIndexHintType,
+ pub index_type: TableIndexType,
+ pub for_clause: Option<TableIndexHintForClause>,
+ pub index_names: Vec<Ident>,
+}
+
+impl fmt::Display for TableIndexHints {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} {} ", self.hint_type, self.index_type)?;
+ if let Some(for_clause) = &self.for_clause {
+ write!(f, "FOR {} ", for_clause)?;
+ }
+ write!(f, "({})", display_comma_separated(&self.index_names))
+ }
+}
+
/// A table name or a parenthesized subquery with an optional alias
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -1009,6 +1084,9 @@ pub enum TableFactor {
/// Optional table sample modifier
/// See:
<https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#sample-clause>
sample: Option<TableSampleKind>,
+ /// Optional index hints(mysql)
+ /// See: <https://dev.mysql.com/doc/refman/8.4/en/index-hints.html>
+ index_hints: Vec<TableIndexHints>,
},
Derived {
lateral: bool,
@@ -1590,6 +1668,7 @@ impl fmt::Display for TableFactor {
with_ordinality,
json_path,
sample,
+ index_hints,
} => {
write!(f, "{name}")?;
if let Some(json_path) = json_path {
@@ -1618,6 +1697,9 @@ impl fmt::Display for TableFactor {
if let Some(alias) = alias {
write!(f, " AS {alias}")?;
}
+ if !index_hints.is_empty() {
+ write!(f, " {}", display_separated(index_hints, " "))?;
+ }
if !with_hints.is_empty() {
write!(f, " WITH ({})",
display_comma_separated(with_hints))?;
}
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index aed1c6c2..8f72c26f 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1739,6 +1739,7 @@ impl Spanned for TableFactor {
partitions: _,
json_path: _,
sample: _,
+ index_hints: _,
} => union_spans(
name.0
.iter()
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 9fc16cd5..6329c5cf 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -854,6 +854,10 @@ pub trait Dialect: Debug + Any {
fn supports_string_escape_constant(&self) -> bool {
false
}
+ /// Returns true if the dialect supports the table hints in the `FROM`
clause.
+ fn supports_table_hints(&self) -> bool {
+ false
+ }
}
/// This represents the operators for which precedence must be defined
diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs
index 535b4298..a67fe67b 100644
--- a/src/dialect/mysql.rs
+++ b/src/dialect/mysql.rs
@@ -25,6 +25,10 @@ use crate::{
parser::{Parser, ParserError},
};
+use super::keywords;
+
+const RESERVED_FOR_TABLE_ALIAS_MYSQL: &[Keyword] = &[Keyword::USE,
Keyword::IGNORE, Keyword::FORCE];
+
/// A [`Dialect`] for [MySQL](https://www.mysql.com/)
#[derive(Debug)]
pub struct MySqlDialect {}
@@ -111,6 +115,16 @@ impl Dialect for MySqlDialect {
fn supports_user_host_grantee(&self) -> bool {
true
}
+
+ fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser:
&mut Parser) -> bool {
+ explicit
+ || (!keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw)
+ && !RESERVED_FOR_TABLE_ALIAS_MYSQL.contains(kw))
+ }
+
+ fn supports_table_hints(&self) -> bool {
+ true
+ }
}
/// `LOCK TABLES`
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index c6e1eb19..c8ff01f7 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -8910,6 +8910,64 @@ impl<'a> Parser<'a> {
}
}
+ fn parse_table_index_hints(&mut self) -> Result<Vec<TableIndexHints>,
ParserError> {
+ let mut hints = vec![];
+ while let Some(hint_type) =
+ self.parse_one_of_keywords(&[Keyword::USE, Keyword::IGNORE,
Keyword::FORCE])
+ {
+ let hint_type = match hint_type {
+ Keyword::USE => TableIndexHintType::Use,
+ Keyword::IGNORE => TableIndexHintType::Ignore,
+ Keyword::FORCE => TableIndexHintType::Force,
+ _ => {
+ return self.expected(
+ "expected to match USE/IGNORE/FORCE keyword",
+ self.peek_token(),
+ )
+ }
+ };
+ let index_type = match
self.parse_one_of_keywords(&[Keyword::INDEX, Keyword::KEY]) {
+ Some(Keyword::INDEX) => TableIndexType::Index,
+ Some(Keyword::KEY) => TableIndexType::Key,
+ _ => {
+ return self.expected("expected to match INDEX/KEY
keyword", self.peek_token())
+ }
+ };
+ let for_clause = if self.parse_keyword(Keyword::FOR) {
+ let clause = if self.parse_keyword(Keyword::JOIN) {
+ TableIndexHintForClause::Join
+ } else if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
+ TableIndexHintForClause::OrderBy
+ } else if self.parse_keywords(&[Keyword::GROUP, Keyword::BY]) {
+ TableIndexHintForClause::GroupBy
+ } else {
+ return self.expected(
+ "expected to match FOR/ORDER BY/GROUP BY table hint in
for clause",
+ self.peek_token(),
+ );
+ };
+ Some(clause)
+ } else {
+ None
+ };
+
+ self.expect_token(&Token::LParen)?;
+ let index_names = if self.peek_token().token != Token::RParen {
+ self.parse_comma_separated(Parser::parse_identifier)?
+ } else {
+ vec![]
+ };
+ self.expect_token(&Token::RParen)?;
+ hints.push(TableIndexHints {
+ hint_type,
+ index_type,
+ for_clause,
+ index_names,
+ });
+ }
+ Ok(hints)
+ }
+
/// Wrapper for parse_optional_alias_inner, left for
backwards-compatibility
/// but new flows should use the context-specific methods such as
`maybe_parse_select_item_alias`
/// and `maybe_parse_table_alias`.
@@ -11257,6 +11315,14 @@ impl<'a> Parser<'a> {
let alias = self.maybe_parse_table_alias()?;
+ // MYSQL-specific table hints:
+ let index_hints = if self.dialect.supports_table_hints() {
+ self.maybe_parse(|p| p.parse_table_index_hints())?
+ .unwrap_or(vec![])
+ } else {
+ vec![]
+ };
+
// MSSQL-specific table hints:
let mut with_hints = vec![];
if self.parse_keyword(Keyword::WITH) {
@@ -11285,6 +11351,7 @@ impl<'a> Parser<'a> {
with_ordinality,
json_path,
sample,
+ index_hints,
};
while let Some(kw) = self.parse_one_of_keywords(&[Keyword::PIVOT,
Keyword::UNPIVOT]) {
diff --git a/src/test_utils.rs b/src/test_utils.rs
index f2e3adf0..20898422 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -362,6 +362,7 @@ pub fn table(name: impl Into<String>) -> TableFactor {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
}
}
@@ -376,6 +377,7 @@ pub fn table_from_name(name: ObjectName) -> TableFactor {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
}
}
@@ -393,6 +395,7 @@ pub fn table_with_alias(name: impl Into<String>, alias:
impl Into<String>) -> Ta
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
}
}
diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs
index cbb96376..45d87a8b 100644
--- a/tests/sqlparser_bigquery.rs
+++ b/tests/sqlparser_bigquery.rs
@@ -1565,6 +1565,7 @@ fn parse_table_time_travel() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
joins: vec![]
},]
@@ -1665,6 +1666,7 @@ fn parse_merge() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
table
);
@@ -1682,6 +1684,7 @@ fn parse_merge() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
source
);
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 5c11b290..2489ce2d 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -503,6 +503,7 @@ fn parse_update_with_table_alias() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
joins: vec![],
},
@@ -596,6 +597,7 @@ fn parse_select_with_table_alias() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
joins: vec![],
}]
@@ -792,6 +794,7 @@ fn parse_where_delete_with_alias_statement() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
from[0].relation,
);
@@ -810,6 +813,7 @@ fn parse_where_delete_with_alias_statement() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
joins: vec![],
}]),
@@ -6416,6 +6420,7 @@ fn parse_joins_on() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
global,
join_operator: f(JoinConstraint::On(Expr::BinaryOp {
@@ -6545,6 +6550,7 @@ fn parse_joins_using() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
global: false,
join_operator: f(JoinConstraint::Using(vec![ObjectName::from(vec![
@@ -6623,6 +6629,7 @@ fn parse_natural_join() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
global: false,
join_operator: f(JoinConstraint::Natural),
@@ -8718,6 +8725,7 @@ fn parse_merge() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
}
);
assert_eq!(table, table_no_into);
@@ -9901,6 +9909,7 @@ fn parse_pivot_table() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
}),
aggregate_functions: vec![
expected_function("a", None),
@@ -9977,6 +9986,7 @@ fn parse_unpivot_table() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
}),
value: Ident {
value: "quantity".to_string(),
@@ -10023,6 +10033,73 @@ fn parse_unpivot_table() {
);
}
+#[test]
+fn parse_select_table_with_index_hints() {
+ let supported_dialects = all_dialects_where(|d| d.supports_table_hints());
+ let s = supported_dialects.verified_only_select(
+ "SELECT * FROM t1 USE INDEX (i1) IGNORE INDEX FOR ORDER BY (i2) ORDER
BY a",
+ );
+ if let TableFactor::Table { index_hints, .. } = &s.from[0].relation {
+ assert_eq!(
+ vec![
+ TableIndexHints {
+ hint_type: TableIndexHintType::Use,
+ index_names: vec!["i1".into()],
+ index_type: TableIndexType::Index,
+ for_clause: None,
+ },
+ TableIndexHints {
+ hint_type: TableIndexHintType::Ignore,
+ index_names: vec!["i2".into()],
+ index_type: TableIndexType::Index,
+ for_clause: Some(TableIndexHintForClause::OrderBy),
+ },
+ ],
+ *index_hints
+ );
+ } else {
+ panic!("Expected TableFactor::Table");
+ }
+ supported_dialects.verified_stmt("SELECT * FROM t1 USE INDEX (i1) USE
INDEX (i1, i1)");
+ supported_dialects.verified_stmt(
+ "SELECT * FROM t1 USE INDEX () IGNORE INDEX (i2) USE INDEX (i1) USE
INDEX (i2)",
+ );
+ supported_dialects.verified_stmt("SELECT * FROM t1 FORCE INDEX FOR JOIN
(i2)");
+ supported_dialects.verified_stmt("SELECT * FROM t1 IGNORE INDEX FOR JOIN
(i2)");
+ supported_dialects.verified_stmt(
+ "SELECT * FROM t USE INDEX (index1) IGNORE INDEX FOR ORDER BY (index1)
IGNORE INDEX FOR GROUP BY (index1) WHERE A = B",
+ );
+
+ // Test that dialects that don't support table hints will keep parsing the
USE as table alias
+ let sql = "SELECT * FROM T USE LIMIT 1";
+ let unsupported_dialects = all_dialects_where(|d|
!d.supports_table_hints());
+ let select = unsupported_dialects
+ .verified_only_select_with_canonical(sql, "SELECT * FROM T AS USE
LIMIT 1");
+ assert_eq!(
+ select.from,
+ vec![TableWithJoins {
+ relation: TableFactor::Table {
+ name:
ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(
+ Ident::new("T")
+ )]),
+ alias: Some(TableAlias {
+ name: Ident::new("USE"),
+ columns: vec![],
+ }),
+ args: None,
+ with_hints: vec![],
+ version: None,
+ partitions: vec![],
+ with_ordinality: false,
+ json_path: None,
+ sample: None,
+ index_hints: vec![],
+ },
+ joins: vec![],
+ }]
+ );
+}
+
#[test]
fn parse_pivot_unpivot_table() {
let sql = concat!(
@@ -10048,6 +10125,7 @@ fn parse_pivot_unpivot_table() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
}),
value: Ident {
value: "population".to_string(),
diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs
index 9c4e8f07..5d710b17 100644
--- a/tests/sqlparser_hive.rs
+++ b/tests/sqlparser_hive.rs
@@ -460,6 +460,7 @@ fn parse_delimited_identifiers() {
partitions: _,
json_path: _,
sample: _,
+ index_hints: _,
} => {
assert_eq!(
ObjectName::from(vec![Ident::with_quote('"', "a table")]),
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 3c401759..9046e9e7 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -74,6 +74,7 @@ fn parse_table_time_travel() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![]
},
joins: vec![]
},]
@@ -223,6 +224,7 @@ fn parse_mssql_openjson() {
partitions: vec![],
json_path: None,
sample: None,
+ index_hints: vec![]
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
@@ -282,6 +284,7 @@ fn parse_mssql_openjson() {
partitions: vec![],
json_path: None,
sample: None,
+ index_hints: vec![]
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
@@ -341,6 +344,7 @@ fn parse_mssql_openjson() {
partitions: vec![],
json_path: None,
sample: None,
+ index_hints: vec![]
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
@@ -400,6 +404,7 @@ fn parse_mssql_openjson() {
partitions: vec![],
json_path: None,
sample: None,
+ index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
@@ -439,6 +444,7 @@ fn parse_mssql_openjson() {
partitions: vec![],
json_path: None,
sample: None,
+ index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::OpenJsonTable {
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index fb72436e..501dce3e 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -2036,6 +2036,7 @@ fn parse_update_with_joins() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
joins: vec![Join {
relation: TableFactor::Table {
@@ -2051,6 +2052,7 @@ fn parse_update_with_joins() {
with_ordinality: false,
json_path: None,
sample: None,
+ index_hints: vec![],
},
global: false,
join_operator:
JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]