feat: add FULLTEXT option on create table for MySQL and Generic dialects (#702)

This commit is contained in:
Augusto Fotino 2022-11-11 18:03:39 -03:00 committed by GitHub
parent 87b4a168cb
commit cdf4447065
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 6 deletions

View file

@ -270,6 +270,28 @@ pub enum TableConstraint {
/// Referred column identifier list. /// Referred column identifier list.
columns: Vec<Ident>, columns: Vec<Ident>,
}, },
/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
/// and MySQL displays both the same way, it is part of this definition as well.
///
/// Supported syntax:
///
/// ```markdown
/// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
///
/// key_part: col_name
/// ```
///
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
FulltextOrSpatial {
/// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition.
fulltext: bool,
/// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
index_type_display: KeyOrIndexDisplay,
/// Optional index name.
opt_index_name: Option<Ident>,
/// Referred column identifier list.
columns: Vec<Ident>,
},
} }
impl fmt::Display for TableConstraint { impl fmt::Display for TableConstraint {
@ -330,6 +352,64 @@ impl fmt::Display for TableConstraint {
Ok(()) Ok(())
} }
Self::FulltextOrSpatial {
fulltext,
index_type_display,
opt_index_name,
columns,
} => {
if *fulltext {
write!(f, "FULLTEXT")?;
} else {
write!(f, "SPATIAL")?;
}
if !matches!(index_type_display, KeyOrIndexDisplay::None) {
write!(f, " {}", index_type_display)?;
}
if let Some(name) = opt_index_name {
write!(f, " {}", name)?;
}
write!(f, " ({})", display_comma_separated(columns))?;
Ok(())
}
}
}
}
/// Representation whether a definition can can contains the KEY or INDEX keywords with the same
/// meaning.
///
/// This enum initially is directed to `FULLTEXT`,`SPATIAL`, and `UNIQUE` indexes on create table
/// statements of `MySQL` [(1)].
///
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyOrIndexDisplay {
/// Nothing to display
None,
/// Display the KEY keyword
Key,
/// Display the INDEX keyword
Index,
}
impl fmt::Display for KeyOrIndexDisplay {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
KeyOrIndexDisplay::None => {
write!(f, "")
}
KeyOrIndexDisplay::Key => {
write!(f, "KEY")
}
KeyOrIndexDisplay::Index => {
write!(f, "INDEX")
}
} }
} }
} }

View file

@ -27,7 +27,7 @@ pub use self::data_type::{
}; };
pub use self::ddl::{ pub use self::ddl::{
AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, IndexType, AlterColumnOperation, AlterTableOperation, ColumnDef, ColumnOption, ColumnOptionDef, IndexType,
ReferentialAction, TableConstraint, KeyOrIndexDisplay, ReferentialAction, TableConstraint,
}; };
pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{ pub use self::query::{

View file

@ -255,6 +255,7 @@ define_keywords!(
FREEZE, FREEZE,
FROM, FROM,
FULL, FULL,
FULLTEXT,
FUNCTION, FUNCTION,
FUNCTIONS, FUNCTIONS,
FUSION, FUSION,
@ -498,6 +499,7 @@ define_keywords!(
SNAPSHOT, SNAPSHOT,
SOME, SOME,
SORT, SORT,
SPATIAL,
SPECIFIC, SPECIFIC,
SPECIFICTYPE, SPECIFICTYPE,
SQL, SQL,

View file

@ -3085,6 +3085,38 @@ impl<'a> Parser<'a> {
columns, columns,
})) }))
} }
Token::Word(w)
if (w.keyword == Keyword::FULLTEXT || w.keyword == Keyword::SPATIAL)
&& dialect_of!(self is GenericDialect | MySqlDialect) =>
{
if let Some(name) = name {
return self.expected(
"FULLTEXT or SPATIAL option without constraint name",
Token::make_keyword(&name.to_string()),
);
}
let fulltext = w.keyword == Keyword::FULLTEXT;
let index_type_display = if self.parse_keyword(Keyword::KEY) {
KeyOrIndexDisplay::Key
} else if self.parse_keyword(Keyword::INDEX) {
KeyOrIndexDisplay::Index
} else {
KeyOrIndexDisplay::None
};
let opt_index_name = self.maybe_parse(|parser| parser.parse_identifier());
let columns = self.parse_parenthesized_column_list(Mandatory)?;
Ok(Some(TableConstraint::FulltextOrSpatial {
fulltext,
index_type_display,
opt_index_name,
columns,
}))
}
unexpected => { unexpected => {
if name.is_some() { if name.is_some() {
self.expected("PRIMARY, UNIQUE, FOREIGN, or CHECK", unexpected) self.expected("PRIMARY, UNIQUE, FOREIGN, or CHECK", unexpected)

View file

@ -14,16 +14,15 @@
//! Test SQL syntax specific to MySQL. The parser based on the generic dialect //! Test SQL syntax specific to MySQL. The parser based on the generic dialect
//! is also tested (on the inputs it can handle). //! is also tested (on the inputs it can handle).
#[macro_use]
mod test_utils;
use test_utils::*;
use sqlparser::ast::Expr; use sqlparser::ast::Expr;
use sqlparser::ast::Value; use sqlparser::ast::Value;
use sqlparser::ast::*; use sqlparser::ast::*;
use sqlparser::dialect::{GenericDialect, MySqlDialect}; use sqlparser::dialect::{GenericDialect, MySqlDialect};
use sqlparser::tokenizer::Token; use sqlparser::tokenizer::Token;
use test_utils::*;
#[macro_use]
mod test_utils;
#[test] #[test]
fn parse_identifiers() { fn parse_identifiers() {
@ -1129,6 +1128,48 @@ fn parse_create_table_with_index_definition() {
); );
} }
#[test]
fn parse_create_table_with_fulltext_definition() {
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT INDEX (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT KEY (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT potato (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT INDEX potato (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, FULLTEXT KEY potato (id))");
mysql_and_generic()
.verified_stmt("CREATE TABLE tb (c1 INT, c2 INT, FULLTEXT KEY potato (c1, c2))");
}
#[test]
fn parse_create_table_with_spatial_definition() {
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL INDEX (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL KEY (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL potato (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL INDEX potato (id))");
mysql_and_generic().verified_stmt("CREATE TABLE tb (id INT, SPATIAL KEY potato (id))");
mysql_and_generic()
.verified_stmt("CREATE TABLE tb (c1 INT, c2 INT, SPATIAL KEY potato (c1, c2))");
}
#[test]
#[should_panic = "Expected FULLTEXT or SPATIAL option without constraint name, found: cons"]
fn parse_create_table_with_fulltext_definition_should_not_accept_constraint_name() {
mysql_and_generic().verified_stmt("CREATE TABLE tb (c1 INT, CONSTRAINT cons FULLTEXT (c1))");
}
fn mysql() -> TestedDialects { fn mysql() -> TestedDialects {
TestedDialects { TestedDialects {
dialects: vec![Box::new(MySqlDialect {})], dialects: vec![Box::new(MySqlDialect {})],