Skip to content

Commit cdf4447

Browse files
authored
feat: add FULLTEXT option on create table for MySQL and Generic dialects (apache#702)
1 parent 87b4a16 commit cdf4447

5 files changed

Lines changed: 161 additions & 6 deletions

File tree

src/ast/ddl.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,28 @@ pub enum TableConstraint {
270270
/// Referred column identifier list.
271271
columns: Vec<Ident>,
272272
},
273+
/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
274+
/// and MySQL displays both the same way, it is part of this definition as well.
275+
///
276+
/// Supported syntax:
277+
///
278+
/// ```markdown
279+
/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
280+
///
281+
/// key_part: col_name
282+
/// ```
283+
///
284+
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
285+
FulltextOrSpatial {
286+
/// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition.
287+
fulltext: bool,
288+
/// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
289+
index_type_display: KeyOrIndexDisplay,
290+
/// Optional index name.
291+
opt_index_name: Option<Ident>,
292+
/// Referred column identifier list.
293+
columns: Vec<Ident>,
294+
},
273295
}
274296

275297
impl fmt::Display for TableConstraint {
@@ -330,6 +352,64 @@ impl fmt::Display for TableConstraint {
330352

331353
Ok(())
332354
}
355+
Self::FulltextOrSpatial {
356+
fulltext,
357+
index_type_display,
358+
opt_index_name,
359+
columns,
360+
} => {
361+
if *fulltext {
362+
write!(f, "FULLTEXT")?;
363+
} else {
364+
write!(f, "SPATIAL")?;
365+
}
366+
367+
if !matches!(index_type_display, KeyOrIndexDisplay::None) {
368+
write!(f, " {}", index_type_display)?;
369+
}
370+
371+
if let Some(name) = opt_index_name {
372+
write!(f, " {}", name)?;
373+
}
374+
375+
write!(f, " ({})", display_comma_separated(columns))?;
376+
377+
Ok(())
378+
}
379+
}
380+
}
381+
}
382+
383+
/// Representation whether a definition can can contains the KEY or INDEX keywords with the same
384+
/// meaning.
385+
///
386+
/// This enum initially is directed to `FULLTEXT`,`SPATIAL`, and `UNIQUE` indexes on create table
387+
/// statements of `MySQL` [(1)].
388+
///
389+
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
390+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
391+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
392+
pub enum KeyOrIndexDisplay {
393+
/// Nothing to display
394+
None,
395+
/// Display the KEY keyword
396+
Key,
397+
/// Display the INDEX keyword
398+
Index,
399+
}
400+
401+
impl fmt::Display for KeyOrIndexDisplay {
402+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
403+
match self {
404+
KeyOrIndexDisplay::None => {
405+
write!(f, "")
406+
}
407+
KeyOrIndexDisplay::Key => {
408+
write!(f, "KEY")
409+
}
410+
KeyOrIndexDisplay::Index => {
411+
write!(f, "INDEX")
412+
}
333413
}
334414
}
335415
}

src/ast/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub use self::data_type::{
2727
};
2828
pub use self::ddl::{
2929
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, IndexType,
30-
ReferentialAction, TableConstraint,
30+
KeyOrIndexDisplay, ReferentialAction, TableConstraint,
3131
};
3232
pub use self::operator::{BinaryOperator, UnaryOperator};
3333
pub use self::query::{

src/keywords.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ define_keywords!(
255255
FREEZE,
256256
FROM,
257257
FULL,
258+
FULLTEXT,
258259
FUNCTION,
259260
FUNCTIONS,
260261
FUSION,
@@ -498,6 +499,7 @@ define_keywords!(
498499
SNAPSHOT,
499500
SOME,
500501
SORT,
502+
SPATIAL,
501503
SPECIFIC,
502504
SPECIFICTYPE,
503505
SQL,

src/parser.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3085,6 +3085,38 @@ impl<'a> Parser<'a> {
30853085
columns,
30863086
}))
30873087
}
3088+
Token::Word(w)
3089+
if (w.keyword == Keyword::FULLTEXT || w.keyword == Keyword::SPATIAL)
3090+
&& dialect_of!(self is GenericDialect | MySqlDialect) =>
3091+
{
3092+
if let Some(name) = name {
3093+
return self.expected(
3094+
"FULLTEXT or SPATIAL option without constraint name",
3095+
Token::make_keyword(&name.to_string()),
3096+
);
3097+
}
3098+
3099+
let fulltext = w.keyword == Keyword::FULLTEXT;
3100+
3101+
let index_type_display = if self.parse_keyword(Keyword::KEY) {
3102+
KeyOrIndexDisplay::Key
3103+
} else if self.parse_keyword(Keyword::INDEX) {
3104+
KeyOrIndexDisplay::Index
3105+
} else {
3106+
KeyOrIndexDisplay::None
3107+
};
3108+
3109+
let opt_index_name = self.maybe_parse(|parser| parser.parse_identifier());
3110+
3111+
let columns = self.parse_parenthesized_column_list(Mandatory)?;
3112+
3113+
Ok(Some(TableConstraint::FulltextOrSpatial {
3114+
fulltext,
3115+
index_type_display,
3116+
opt_index_name,
3117+
columns,
3118+
}))
3119+
}
30883120
unexpected => {
30893121
if name.is_some() {
30903122
self.expected("PRIMARY, UNIQUE, FOREIGN, or CHECK", unexpected)

tests/sqlparser_mysql.rs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@
1414
//! Test SQL syntax specific to MySQL. The parser based on the generic dialect
1515
//! is also tested (on the inputs it can handle).
1616
17-
#[macro_use]
18-
mod test_utils;
19-
20-
use test_utils::*;
21-
2217
use sqlparser::ast::Expr;
2318
use sqlparser::ast::Value;
2419
use sqlparser::ast::*;
2520
use sqlparser::dialect::{GenericDialect, MySqlDialect};
2621
use sqlparser::tokenizer::Token;
22+
use test_utils::*;
23+
24+
#[macro_use]
25+
mod test_utils;
2726

2827
#[test]
2928
fn parse_identifiers() {
@@ -1129,6 +1128,48 @@ fn parse_create_table_with_index_definition() {
11291128
);
11301129
}
11311130

1131+
#[test]
1132+
fn parse_create_table_with_fulltext_definition() {
1133+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT (id))");
1134+
1135+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT INDEX (id))");
1136+
1137+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT KEY (id))");
1138+
1139+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT potato (id))");
1140+
1141+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT INDEX potato (id))");
1142+
1143+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT KEY potato (id))");
1144+
1145+
mysql_and_generic()
1146+
.verified_stmt("CREATE TABLE tb (c1 INT, c2 INT, FULLTEXT KEY potato (c1, c2))");
1147+
}
1148+
1149+
#[test]
1150+
fn parse_create_table_with_spatial_definition() {
1151+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL (id))");
1152+
1153+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL INDEX (id))");
1154+
1155+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL KEY (id))");
1156+
1157+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL potato (id))");
1158+
1159+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL INDEX potato (id))");
1160+
1161+
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL KEY potato (id))");
1162+
1163+
mysql_and_generic()
1164+
.verified_stmt("CREATE TABLE tb (c1 INT, c2 INT, SPATIAL KEY potato (c1, c2))");
1165+
}
1166+
1167+
#[test]
1168+
#[should_panic = "Expected FULLTEXT or SPATIAL option without constraint name, found: cons"]
1169+
fn parse_create_table_with_fulltext_definition_should_not_accept_constraint_name() {
1170+
mysql_and_generic().verified_stmt("CREATE TABLE tb (c1 INT, CONSTRAINT cons FULLTEXT (c1))");
1171+
}
1172+
11321173
fn mysql() -> TestedDialects {
11331174
TestedDialects {
11341175
dialects: vec![Box::new(MySqlDialect {})],

0 commit comments

Comments
 (0)