Skip to content

Commit

Permalink
Implement Index Hints for Mysql (#636)
Browse files Browse the repository at this point in the history
* Implement Index Hints for Mysql

* Address review: use a struct and rename IndexHint enums
  • Loading branch information
VincentFoulon80 authored Dec 14, 2023
1 parent c883c4a commit b143d21
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 0 deletions.
45 changes: 45 additions & 0 deletions src/backend/mysql/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,51 @@ impl QueryBuilder for MysqlQueryBuilder {
};
}

fn prepare_index_hints(&self, hints: &[IndexHint], sql: &mut dyn SqlWriter) {
hints.iter().fold(true, |first, hint| {
if !first {
write!(sql, " ").unwrap()
}
match hint.r#type {
IndexHintType::Use => {
write!(sql, "USE INDEX ",).unwrap();
self.prepare_index_hint_scope(&hint.scope, sql);
write!(sql, "(").unwrap();
hint.index.prepare(sql.as_writer(), self.quote());
}
IndexHintType::Ignore => {
write!(sql, "IGNORE INDEX ",).unwrap();
self.prepare_index_hint_scope(&hint.scope, sql);
write!(sql, "(").unwrap();
hint.index.prepare(sql.as_writer(), self.quote());
}
IndexHintType::Force => {
write!(sql, "FORCE INDEX ",).unwrap();
self.prepare_index_hint_scope(&hint.scope, sql);
write!(sql, "(").unwrap();
hint.index.prepare(sql.as_writer(), self.quote());
}
}
write!(sql, ")").unwrap();
false
});
}

fn prepare_index_hint_scope(&self, index_hint_scope: &IndexHintScope, sql: &mut dyn SqlWriter) {
match index_hint_scope {
IndexHintScope::Join => {
write!(sql, "FOR JOIN ").unwrap();
}
IndexHintScope::OrderBy => {
write!(sql, "FOR ORDER BY ").unwrap();
}
IndexHintScope::GroupBy => {
write!(sql, "FOR GROUP BY ").unwrap();
}
IndexHintScope::All => {}
}
}

fn prepare_query_statement(&self, query: &SubQueryStatement, sql: &mut dyn SqlWriter) {
query.prepare_statement(self, sql);
}
Expand Down
15 changes: 15 additions & 0 deletions src/backend/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ pub trait QueryBuilder:
self.prepare_table_ref(table_ref, sql);
false
});
if !select.index_hints.is_empty() {
write!(sql, " ").unwrap();
self.prepare_index_hints(&select.index_hints, sql);
}
}

if !select.join.is_empty() {
Expand Down Expand Up @@ -422,6 +426,17 @@ pub trait QueryBuilder:
}
}

/// Translate [`IndexHint`] into SQL statement.
fn prepare_index_hints(&self, _hints: &[IndexHint], _sql: &mut dyn SqlWriter) {}

/// Translate [`IndexHintType`] into SQL statement.
fn prepare_index_hint_scope(
&self,
_index_hint_scope: &IndexHintScope,
_sql: &mut dyn SqlWriter,
) {
}

/// Translate [`LockType`] into SQL statement.
fn prepare_select_lock(&self, lock: &LockClause, sql: &mut dyn SqlWriter) {
write!(
Expand Down
123 changes: 123 additions & 0 deletions src/query/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct SelectStatement {
pub(crate) distinct: Option<SelectDistinct>,
pub(crate) selects: Vec<SelectExpr>,
pub(crate) from: Vec<TableRef>,
pub(crate) index_hints: Vec<IndexHint>,
pub(crate) join: Vec<JoinExpr>,
pub(crate) r#where: ConditionHolder,
pub(crate) groups: Vec<SimpleExpr>,
Expand Down Expand Up @@ -82,6 +83,28 @@ pub struct SelectExpr {
pub window: Option<WindowSelectType>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct IndexHint {
pub index: DynIden,
pub r#type: IndexHintType,
pub scope: IndexHintScope,
}

#[derive(Debug, Clone, PartialEq)]
pub enum IndexHintType {
Use,
Ignore,
Force,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IndexHintScope {
Join,
OrderBy,
GroupBy,
All,
}

/// Join expression used in select statement
#[derive(Debug, Clone, PartialEq)]
pub struct JoinExpr {
Expand Down Expand Up @@ -150,6 +173,7 @@ impl SelectStatement {
distinct: self.distinct.take(),
selects: std::mem::take(&mut self.selects),
from: std::mem::take(&mut self.from),
index_hints: std::mem::take(&mut self.index_hints),
join: std::mem::take(&mut self.join),
r#where: std::mem::replace(&mut self.r#where, ConditionHolder::new()),
groups: std::mem::take(&mut self.groups),
Expand Down Expand Up @@ -302,6 +326,105 @@ impl SelectStatement {
self
}

/// Use index hint for *MYSQL ONLY*
///
/// Give the optimizer information about how to choose indexes during query processing.
/// See [MySQL reference manual for Index Hints](https://dev.mysql.com/doc/refman/8.0/en/index-hints.html)
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::select()
/// .from(Char::Table)
/// .use_index(IndexName::new("IDX_123456"), IndexHintScope::All)
/// .column(Char::SizeW)
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"SELECT `size_w` FROM `character` USE INDEX (`IDX_123456`)"#
/// );
/// ```
pub fn use_index<I>(&mut self, index: I, scope: IndexHintScope) -> &mut Self
where
I: IntoIden,
{
self.index_hints.push(IndexHint {
index: index.into_iden(),
r#type: IndexHintType::Use,
scope,
});
self
}

/// Force index hint for *MYSQL ONLY*
///
/// Give the optimizer information about how to choose indexes during query processing.
/// See [MySQL reference manual for Index Hints](https://dev.mysql.com/doc/refman/8.0/en/index-hints.html)
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::select()
/// .from(Char::Table)
/// .force_index(IndexName::new("IDX_123456"), IndexHintScope::All)
/// .column(Char::SizeW)
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"SELECT `size_w` FROM `character` FORCE INDEX (`IDX_123456`)"#
/// );
/// ```
pub fn force_index<I>(&mut self, index: I, scope: IndexHintScope) -> &mut Self
where
I: IntoIden,
{
self.index_hints.push(IndexHint {
index: index.into_iden(),
r#type: IndexHintType::Force,
scope,
});
self
}

/// Ignore index hint for *MYSQL ONLY*
///
/// Give the optimizer information about how to choose indexes during query processing.
/// See [MySQL reference manual for Index Hints](https://dev.mysql.com/doc/refman/8.0/en/index-hints.html)
///
/// # Examples
///
/// ```
/// use sea_query::{tests_cfg::*, *};
///
/// let query = Query::select()
/// .from(Char::Table)
/// .ignore_index(IndexName::new("IDX_123456"), IndexHintScope::All)
/// .column(Char::SizeW)
/// .to_owned();
///
/// assert_eq!(
/// query.to_string(MysqlQueryBuilder),
/// r#"SELECT `size_w` FROM `character` IGNORE INDEX (`IDX_123456`)"#
/// )
/// ```
pub fn ignore_index<I>(&mut self, index: I, scope: IndexHintScope) -> &mut Self
where
I: IntoIden,
{
self.index_hints.push(IndexHint {
index: index.into_iden(),
r#type: IndexHintType::Ignore,
scope,
});
self
}

/// Select distinct on for *POSTGRES ONLY*
///
/// # Examples
Expand Down
3 changes: 3 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ pub enum Order {
#[derive(Debug, Clone)]
pub struct Alias(String);

/// Helper for create index names
pub type IndexName = Alias;

/// Null Alias
#[derive(Default, Debug, Copy, Clone)]
pub struct NullAlias;
Expand Down
54 changes: 54 additions & 0 deletions tests/mysql/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,60 @@ fn select_58() {
);
}

#[test]
fn select_59() {
assert_eq!(
Query::select()
.columns([Char::Character, Char::SizeW, Char::SizeH])
.from(Char::Table)
.ignore_index(IndexName::new("IDX_123456"), IndexHintScope::All)
.limit(10)
.offset(100)
.to_string(MysqlQueryBuilder),
"SELECT `character`, `size_w`, `size_h` FROM `character` IGNORE INDEX (`IDX_123456`) LIMIT 10 OFFSET 100"
);
}

#[test]
fn select_60() {
assert_eq!(
Query::select()
.columns([Char::Character, Char::SizeW, Char::SizeH])
.from(Char::Table)
.ignore_index(IndexName::new("IDX_123456"), IndexHintScope::All)
.ignore_index(IndexName::new("IDX_789ABC"), IndexHintScope::All)
.limit(10)
.offset(100)
.to_string(MysqlQueryBuilder),
"SELECT `character`, `size_w`, `size_h` FROM `character` IGNORE INDEX (`IDX_123456`) IGNORE INDEX (`IDX_789ABC`) LIMIT 10 OFFSET 100"
);
}

#[test]
fn select_61() {
assert_eq!(
Query::select()
.columns([Char::Character, Char::SizeW, Char::SizeH])
.from(Char::Table)
.ignore_index(IndexName::new("IDX_123456"), IndexHintScope::Join)
.use_index(IndexName::new("IDX_789ABC"), IndexHintScope::GroupBy)
.force_index(IndexName::new("IDX_DEFGHI"), IndexHintScope::OrderBy)
.limit(10)
.offset(100)
.to_string(MysqlQueryBuilder),
[
r#"SELECT `character`, `size_w`, `size_h`"#,
r#"FROM `character`"#,
r#"IGNORE INDEX FOR JOIN (`IDX_123456`)"#,
r#"USE INDEX FOR GROUP BY (`IDX_789ABC`)"#,
r#"FORCE INDEX FOR ORDER BY (`IDX_DEFGHI`)"#,
r#"LIMIT 10"#,
r#"OFFSET 100"#,
]
.join(" ")
);
}

#[test]
#[allow(clippy::approx_constant)]
fn insert_2() {
Expand Down

0 comments on commit b143d21

Please sign in to comment.