Skip to content

Commit 3e96347

Browse files
authored
Merge pull request #18 from antialize/index_hint
Index hint
2 parents 3f2f042 + 4687b44 commit 3e96347

File tree

5 files changed

+225
-30
lines changed

5 files changed

+225
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sql-parse"
3-
version = "0.18.0"
3+
version = "0.19.0"
44
edition = "2021"
55
authors = ["Jakob Truelsen <[email protected]>"]
66
keywords = [ "mysql", "postgresql", "sql", "lexer", "parser" ]

src/drop.rs

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
lexer::Token,
1818
parser::{ParseError, Parser},
1919
qualified_name::parse_qualified_name,
20-
Identifier, QualifiedName, Span, Spanned, Statement,
20+
Identifier, Issue, QualifiedName, Span, Spanned, Statement,
2121
};
2222

2323
/// Represent a drop table statement
@@ -438,16 +438,33 @@ pub(crate) fn parse_drop<'a>(parser: &mut Parser<'a, '_>) -> Result<Statement<'a
438438
None
439439
};
440440
let index_name = parser.consume_plain_identifier()?;
441-
let on_span = parser.consume_keyword(Keyword::ON)?;
442-
let table_name = parse_qualified_name(parser)?;
443-
Ok(Statement::DropIndex(DropIndex {
441+
let on = if let Some(span) = parser.skip_keyword(Keyword::ON) {
442+
let table_name = parse_qualified_name(parser)?;
443+
Some((span, table_name))
444+
} else {
445+
None
446+
};
447+
448+
let v = DropIndex {
444449
drop_span,
445450
index_span,
446451
if_exists,
447452
index_name,
448-
on_span,
449-
table_name,
450-
}))
453+
on,
454+
};
455+
456+
if v.on.is_none() && parser.options.dialect.is_maria() {
457+
parser
458+
.issues
459+
.push(Issue::err("On required for index drops in MariaDb", &v));
460+
}
461+
if v.on.is_some() && parser.options.dialect.is_postgresql() {
462+
parser.issues.push(Issue::err(
463+
"On not supported for index drops in PostgreSQL",
464+
&v,
465+
));
466+
}
467+
Ok(Statement::DropIndex(v))
451468
}
452469
Token::Ident(_, Keyword::PROCEDURE) => {
453470
// TODO complain about temporary
@@ -531,6 +548,47 @@ pub(crate) fn parse_drop<'a>(parser: &mut Parser<'a, '_>) -> Result<Statement<'a
531548
}
532549
}
533550

551+
/// Represent a drop index statement.
552+
///
553+
/// MariaDB example
554+
/// ```
555+
/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropIndex, Statement};
556+
/// # let options = ParseOptions::new().dialect(SQLDialect::MariaDB);
557+
/// # let mut issues = Vec::new();
558+
/// #
559+
/// let sql = "DROP INDEX IF EXISTS `myindex` ON `bar`;";
560+
///
561+
/// let mut stmts = parse_statements(sql, &mut issues, &options);
562+
///
563+
/// # assert!(issues.is_empty());
564+
/// #
565+
/// let s: DropIndex = match stmts.pop() {
566+
/// Some(Statement::DropIndex(s)) => s,
567+
/// _ => panic!("We should get a drop trigger statement")
568+
/// };
569+
///
570+
/// assert!(s.index_name.as_str() == "myindex");
571+
/// ```
572+
///
573+
/// PostgreSQL example
574+
/// ```
575+
/// # use sql_parse::{SQLDialect, SQLArguments, ParseOptions, parse_statements, DropIndex, Statement};
576+
/// # let options = ParseOptions::new().dialect(SQLDialect::PostgreSQL);
577+
/// # let mut issues = Vec::new();
578+
/// #
579+
/// let sql = "DROP INDEX IF EXISTS \"myindex\";";
580+
///
581+
/// let mut stmts = parse_statements(sql, &mut issues, &options);
582+
///
583+
/// # assert!(issues.is_empty(), "{:?}", issues);
584+
/// #
585+
/// let s: DropIndex = match stmts.pop() {
586+
/// Some(Statement::DropIndex(s)) => s,
587+
/// _ => panic!("We should get a drop trigger statement")
588+
/// };
589+
///
590+
/// assert!(s.index_name.as_str() == "myindex");
591+
/// ```
534592
#[derive(Debug, Clone)]
535593
pub struct DropIndex<'a> {
536594
/// Span of "DROP"
@@ -540,8 +598,7 @@ pub struct DropIndex<'a> {
540598
/// Span of "IF EXISTS" if specified
541599
pub if_exists: Option<Span>,
542600
pub index_name: Identifier<'a>,
543-
pub on_span: Span,
544-
pub table_name: QualifiedName<'a>,
601+
pub on: Option<(Span, QualifiedName<'a>)>,
545602
}
546603

547604
impl<'a> Spanned for DropIndex<'a> {
@@ -550,7 +607,6 @@ impl<'a> Spanned for DropIndex<'a> {
550607
.join_span(&self.index_span)
551608
.join_span(&self.if_exists)
552609
.join_span(&self.index_name)
553-
.join_span(&self.on_span)
554-
.join_span(&self.table_name)
610+
.join_span(&self.on)
555611
}
556612
}

src/lib.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ pub use create::{
8686
};
8787
pub use delete::{Delete, DeleteFlag};
8888
pub use drop::{
89-
DropDatabase, DropEvent, DropFunction, DropProcedure, DropServer, DropTable, DropTrigger,
90-
DropView,
89+
DropDatabase, DropEvent, DropFunction, DropIndex, DropProcedure, DropServer, DropTable,
90+
DropTrigger, DropView,
9191
};
9292
pub use expression::{
9393
BinaryOperator, Expression, Function, IdentifierPart, Is, UnaryOperator, Variable, When,
@@ -97,7 +97,10 @@ pub use insert_replace::{
9797
InsertReplaceSetPair, InsertReplaceType, OnConflict, OnConflictAction, OnConflictTarget,
9898
};
9999
pub use rename::{RenameTable, TableToTable};
100-
pub use select::{JoinSpecification, JoinType, Select, SelectExpr, SelectFlag, TableReference};
100+
pub use select::{
101+
IndexHint, IndexHintFor, IndexHintType, IndexHintUse, JoinSpecification, JoinType, Select,
102+
SelectExpr, SelectFlag, TableReference,
103+
};
101104
pub use truncate::TruncateTable;
102105
pub use update::{Update, UpdateFlag};
103106
pub use with_query::{WithBlock, WithQuery};
@@ -381,3 +384,16 @@ pub fn parse_with_statement() {
381384
// assert!(result.is_none(), "result: {:#?}", &result);
382385
assert!(issues.is_empty(), "Issues: {:#?}", issues);
383386
}
387+
388+
#[test]
389+
pub fn parse_use_index() {
390+
let sql = "SELECT `a` FROM `b` FORCE INDEX FOR GROUP BY (`b`, `c`)";
391+
let options = ParseOptions::new()
392+
.dialect(SQLDialect::MariaDB)
393+
.arguments(SQLArguments::QuestionMark)
394+
.warn_unquoted_identifiers(false);
395+
396+
let mut issues = Vec::new();
397+
let _result = parse_statement(sql, &mut issues, &options);
398+
assert!(issues.is_empty(), "Issues: {:#?}", issues);
399+
}

src/select.rs

Lines changed: 137 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,74 @@ impl Spanned for JoinType {
9999
}
100100
}
101101

102+
#[derive(Debug, Clone)]
103+
pub enum IndexHintUse {
104+
Use(Span),
105+
Ignore(Span),
106+
Force(Span),
107+
}
108+
impl Spanned for IndexHintUse {
109+
fn span(&self) -> Span {
110+
match &self {
111+
IndexHintUse::Use(v) => v.span(),
112+
IndexHintUse::Ignore(v) => v.span(),
113+
IndexHintUse::Force(v) => v.span(),
114+
}
115+
}
116+
}
117+
118+
#[derive(Debug, Clone)]
119+
pub enum IndexHintType {
120+
Index(Span),
121+
Key(Span),
122+
}
123+
impl Spanned for IndexHintType {
124+
fn span(&self) -> Span {
125+
match &self {
126+
IndexHintType::Index(v) => v.span(),
127+
IndexHintType::Key(v) => v.span(),
128+
}
129+
}
130+
}
131+
132+
#[derive(Debug, Clone)]
133+
pub enum IndexHintFor {
134+
Join(Span),
135+
OrderBy(Span),
136+
GroupBy(Span),
137+
}
138+
impl Spanned for IndexHintFor {
139+
fn span(&self) -> Span {
140+
match &self {
141+
IndexHintFor::Join(v) => v.span(),
142+
IndexHintFor::OrderBy(v) => v.span(),
143+
IndexHintFor::GroupBy(v) => v.span(),
144+
}
145+
}
146+
}
147+
148+
#[derive(Debug, Clone)]
149+
pub struct IndexHint<'a> {
150+
use_: IndexHintUse,
151+
type_: IndexHintType,
152+
for_: Option<(Span, IndexHintFor)>,
153+
lparen: Span,
154+
index_list: Vec<Identifier<'a>>,
155+
rparen: Span,
156+
}
157+
158+
impl<'a> Spanned for IndexHint<'a> {
159+
fn span(&self) -> Span {
160+
self.use_
161+
.span()
162+
.join_span(&self.type_)
163+
.join_span(&self.for_)
164+
.join_span(&self.lparen)
165+
.join_span(&self.index_list)
166+
.join_span(&self.rparen)
167+
}
168+
}
169+
102170
/// Reference to table in select
103171
#[derive(Debug, Clone)]
104172
pub enum TableReference<'a> {
@@ -110,6 +178,8 @@ pub enum TableReference<'a> {
110178
as_span: Option<Span>,
111179
/// Alias for table if specified
112180
as_: Option<Identifier<'a>>,
181+
/// Index hints
182+
index_hints: Vec<IndexHint<'a>>,
113183
},
114184
/// Subquery
115185
Query {
@@ -141,9 +211,11 @@ impl<'a> Spanned for TableReference<'a> {
141211
identifier,
142212
as_span,
143213
as_,
214+
index_hints,
144215
} => identifier
145216
.opt_join_span(as_span)
146217
.opt_join_span(as_)
218+
.opt_join_span(index_hints)
147219
.expect("span of table"),
148220
TableReference::Query {
149221
query,
@@ -193,19 +265,6 @@ pub(crate) fn parse_table_reference_inner<'a>(
193265
Token::Ident(_, _) => {
194266
let identifier = parse_qualified_name(parser)?;
195267

196-
// index_hint_list:
197-
// index_hint [, index_hint] ...
198-
199-
// index_hint: {
200-
// USE {INDEX|KEY}
201-
// [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
202-
// | {IGNORE|FORCE} {INDEX|KEY}
203-
// [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
204-
// }
205-
206-
// index_list:
207-
// index_name [, index_name] .
208-
209268
// TODO [PARTITION (partition_names)] [[AS] alias]
210269
let as_span = parser.skip_keyword(Keyword::AS);
211270
let as_ = if as_span.is_some()
@@ -216,11 +275,75 @@ pub(crate) fn parse_table_reference_inner<'a>(
216275
None
217276
};
218277

219-
// TODO [index_hint_list]
278+
let mut index_hints = Vec::new();
279+
loop {
280+
let use_ = match parser.token {
281+
Token::Ident(_, Keyword::USE) => IndexHintUse::Use(parser.consume()),
282+
Token::Ident(_, Keyword::IGNORE) => IndexHintUse::Ignore(parser.consume()),
283+
Token::Ident(_, Keyword::FORCE) => IndexHintUse::Force(parser.consume()),
284+
_ => break,
285+
};
286+
let type_ = match parser.token {
287+
Token::Ident(_, Keyword::INDEX) => IndexHintType::Index(parser.consume()),
288+
Token::Ident(_, Keyword::KEY) => IndexHintType::Key(parser.consume()),
289+
_ => parser.expected_failure("'INDEX' or 'KEY'")?,
290+
};
291+
let for_ = if let Some(for_span) = parser.skip_keyword(Keyword::FOR) {
292+
let v = match parser.token {
293+
Token::Ident(_, Keyword::JOIN) => IndexHintFor::Join(parser.consume()),
294+
Token::Ident(_, Keyword::GROUP) => IndexHintFor::GroupBy(
295+
parser.consume_keywords(&[Keyword::GROUP, Keyword::BY])?,
296+
),
297+
Token::Ident(_, Keyword::ORDER) => IndexHintFor::OrderBy(
298+
parser.consume_keywords(&[Keyword::ORDER, Keyword::BY])?,
299+
),
300+
_ => parser.expected_failure("'JOIN', 'GROUP BY' or 'ORDER BY'")?,
301+
};
302+
Some((for_span, v))
303+
} else {
304+
None
305+
};
306+
let lparen = parser.consume_token(Token::LParen)?;
307+
let mut index_list = Vec::new();
308+
loop {
309+
parser.recovered(
310+
"')' or ','",
311+
&|t| matches!(t, Token::RParen | Token::Comma),
312+
|parser| {
313+
index_list.push(parser.consume_plain_identifier()?);
314+
Ok(())
315+
},
316+
)?;
317+
if matches!(parser.token, Token::RParen) {
318+
break;
319+
}
320+
parser.consume_token(Token::Comma)?;
321+
}
322+
let rparen = parser.consume_token(Token::RParen)?;
323+
index_hints.push(IndexHint {
324+
use_,
325+
type_,
326+
for_,
327+
lparen,
328+
index_list,
329+
rparen,
330+
})
331+
}
332+
333+
if !index_hints.is_empty() {
334+
if !parser.options.dialect.is_maria() {
335+
parser.issues.push(Issue::err(
336+
"Index hints only supported by MariaDb",
337+
&index_hints.opt_span().unwrap(),
338+
));
339+
}
340+
}
341+
220342
Ok(TableReference::Table {
221343
identifier,
222344
as_span,
223345
as_,
346+
index_hints,
224347
})
225348
}
226349
_ => parser.expected_failure("subquery or identifier"),

0 commit comments

Comments
 (0)