mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-11 01:38:03 +00:00
feat: add FULLTEXT option on create table for MySQL and Generic dialects (#702)
This commit is contained in:
parent
87b4a168cb
commit
cdf4447065
5 changed files with 161 additions and 6 deletions
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {})],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue