diff --git a/Cargo.lock b/Cargo.lock index bdd59547a9e0..ea6cf9997f8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3159,6 +3159,8 @@ dependencies = [ "serde", "serde_json", "snafu 0.8.4", + "sqlparser 0.45.0 (git+https://github.com/GreptimeTeam/sqlparser-rs.git?rev=54a267ac89c09b11c0c88934690530807185d3e7)", + "sqlparser_derive 0.1.1", ] [[package]] @@ -4252,7 +4254,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "greptime-proto" version = "0.1.0" -source = "git+https://github.com/GreptimeTeam/greptime-proto.git?rev=c437b55725b7f5224fe9d46db21072b4a682ee4b#c437b55725b7f5224fe9d46db21072b4a682ee4b" +source = "git+https://github.com/irenjj/greptime-proto.git?rev=ed13098712a87674e1d41be8c9a8ceb23585c009#ed13098712a87674e1d41be8c9a8ceb23585c009" dependencies = [ "prost 0.12.6", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4b5fb9369e38..4961430ff7ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,7 @@ etcd-client = { version = "0.13" } fst = "0.4.7" futures = "0.3" futures-util = "0.3" -greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "c437b55725b7f5224fe9d46db21072b4a682ee4b" } +greptime-proto = { git = "https://github.com/irenjj/greptime-proto.git", rev = "ed13098712a87674e1d41be8c9a8ceb23585c009" } humantime = "2.1" humantime-serde = "1.1" itertools = "0.10" @@ -151,7 +151,7 @@ reqwest = { version = "0.12", default-features = false, features = [ "stream", "multipart", ] } -# SCRAM-SHA-512 requires https://github.com/dequbed/rsasl/pull/48, https://github.com/influxdata/rskafka/pull/247 +# SCRAM-SHA-512 requires https://github.com/dequbed/rsasl/pull/48, https://github.com/influxdata/rskafka/pull/247 rskafka = { git = "https://github.com/WenyXu/rskafka.git", rev = "940c6030012c5b746fad819fb72e3325b26e39de", features = [ "transport-tls", ] } diff --git a/src/api/src/error.rs b/src/api/src/error.rs index 07e43e477299..711e4791ff42 100644 --- a/src/api/src/error.rs +++ b/src/api/src/error.rs @@ -66,15 +66,23 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Failed to decode proto"))] + DecodeProto { + #[snafu(implicit)] + location: Location, + #[snafu(source)] + error: prost::DecodeError, + }, } impl ErrorExt for Error { fn status_code(&self) -> StatusCode { match self { Error::UnknownColumnDataType { .. } => StatusCode::InvalidArguments, - Error::IntoColumnDataType { .. } | Error::SerializeJson { .. } => { - StatusCode::Unexpected - } + Error::IntoColumnDataType { .. } + | Error::SerializeJson { .. } + | Error::DecodeProto { .. } => StatusCode::Unexpected, Error::ConvertColumnDefaultConstraint { source, .. } | Error::InvalidColumnDefaultConstraint { source, .. } => source.status_code(), } diff --git a/src/api/src/v1/column_def.rs b/src/api/src/v1/column_def.rs index b4d3425215c8..9702a445d9cd 100644 --- a/src/api/src/v1/column_def.rs +++ b/src/api/src/v1/column_def.rs @@ -15,8 +15,10 @@ use std::collections::HashMap; use datatypes::schema::{ - ColumnDefaultConstraint, ColumnSchema, FulltextOptions, COMMENT_KEY, FULLTEXT_KEY, + ChangeFulltextOptions, ColumnDefaultConstraint, ColumnSchema, FulltextAnalyzer, + FulltextOptions, COMMENT_KEY, FULLTEXT_KEY, }; +use greptime_proto::v1::{Analyzer, ChangeFulltext}; use snafu::ResultExt; use crate::error::{self, Result}; @@ -93,6 +95,34 @@ pub fn options_from_fulltext(fulltext: &FulltextOptions) -> Result) -> Result> { + let analyzer = analyzer + .map(Analyzer::try_from) + .transpose() + .context(error::DecodeProtoSnafu)?; + Ok(analyzer.map(|a| match a { + Analyzer::English => FulltextAnalyzer::English, + Analyzer::Chinese => FulltextAnalyzer::Chinese, + })) +} +/// Tries to construct a `ChangeFulltextOptions` from the given `ChangeFulltext`. +pub fn try_as_change_fulltext_options( + ChangeFulltext { + enable, + analyzer, + case_sensitive, + .. + }: &ChangeFulltext, +) -> Result { + let analyzer = try_as_fulltext_option(*analyzer)?; + Ok(ChangeFulltextOptions { + enable: *enable, + analyzer, + case_sensitive: *case_sensitive, + }) +} + #[cfg(test)] mod tests { @@ -148,7 +178,7 @@ mod tests { assert!(options.is_none()); let schema = ColumnSchema::new("test", ConcreteDataType::string_datatype(), true) - .with_fulltext_options(FulltextOptions { + .with_fulltext_options(&FulltextOptions { enable: true, analyzer: FulltextAnalyzer::English, case_sensitive: false, diff --git a/src/common/grpc-expr/src/alter.rs b/src/common/grpc-expr/src/alter.rs index ac49069412cd..8d0145cf86bf 100644 --- a/src/common/grpc-expr/src/alter.rs +++ b/src/common/grpc-expr/src/alter.rs @@ -26,8 +26,8 @@ use table::metadata::TableId; use table::requests::{AddColumnRequest, AlterKind, AlterTableRequest, ChangeColumnTypeRequest}; use crate::error::{ - InvalidColumnDefSnafu, MissingFieldSnafu, MissingTimestampColumnSnafu, Result, - UnknownLocationTypeSnafu, + DecodeProtoSnafu, InvalidColumnDefSnafu, MissingFieldSnafu, MissingTimestampColumnSnafu, + Result, UnknownLocationTypeSnafu, }; const LOCATION_TYPE_FIRST: i32 = LocationType::First as i32; @@ -92,6 +92,11 @@ pub fn alter_expr_to_request(table_id: TableId, expr: AlterExpr) -> Result { AlterKind::RenameTable { new_table_name } } + Kind::ChangeFulltext(change) => AlterKind::ChangeFulltext { + column_name: change.column_name.clone(), + options: column_def::try_as_change_fulltext_options(&change) + .context(DecodeProtoSnafu)?, + }, }; let request = AlterTableRequest { diff --git a/src/common/grpc-expr/src/error.rs b/src/common/grpc-expr/src/error.rs index 2f27c08bbe41..64a0b2e525a9 100644 --- a/src/common/grpc-expr/src/error.rs +++ b/src/common/grpc-expr/src/error.rs @@ -121,8 +121,21 @@ pub enum Error { InvalidFulltextColumnType { column_name: String, column_type: ColumnDataType, + }, + + #[snafu(display("Invalid fulltext options"))] + InvalidFulltextOptions { + source: datatypes::error::Error, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Failed to decode proto"))] + DecodeProto { #[snafu(implicit)] location: Location, + #[snafu(source)] + error: api::error::Error, }, } @@ -145,9 +158,10 @@ impl ErrorExt for Error { StatusCode::InvalidArguments } - Error::UnknownColumnDataType { .. } | Error::InvalidFulltextColumnType { .. } => { - StatusCode::InvalidArguments - } + Error::UnknownColumnDataType { .. } + | Error::InvalidFulltextOptions { .. } + | Error::InvalidFulltextColumnType { .. } + | Error::DecodeProto { .. } => StatusCode::InvalidArguments, } } diff --git a/src/common/meta/src/ddl/alter_table/region_request.rs b/src/common/meta/src/ddl/alter_table/region_request.rs index b4223b8ea05d..a8e9f24a1b06 100644 --- a/src/common/meta/src/ddl/alter_table/region_request.rs +++ b/src/common/meta/src/ddl/alter_table/region_request.rs @@ -106,6 +106,7 @@ fn create_proto_alter_kind( }))) } Kind::RenameTable(_) => Ok(None), + Kind::ChangeFulltext(x) => Ok(Some(alter_request::Kind::ChangeFulltext(x.clone()))), } } diff --git a/src/common/meta/src/ddl/alter_table/update_metadata.rs b/src/common/meta/src/ddl/alter_table/update_metadata.rs index 6ef6e2e7fc88..1c94e9099301 100644 --- a/src/common/meta/src/ddl/alter_table/update_metadata.rs +++ b/src/common/meta/src/ddl/alter_table/update_metadata.rs @@ -52,6 +52,7 @@ impl AlterTableProcedure { new_info.name = new_table_name.to_string(); } AlterKind::DropColumns { .. } | AlterKind::ChangeColumnTypes { .. } => {} + AlterKind::ChangeFulltext { .. } => {} } Ok(new_info) diff --git a/src/datatypes/Cargo.toml b/src/datatypes/Cargo.toml index 281057ce80c9..7449dc839938 100644 --- a/src/datatypes/Cargo.toml +++ b/src/datatypes/Cargo.toml @@ -30,3 +30,5 @@ paste = "1.0" serde.workspace = true serde_json.workspace = true snafu.workspace = true +sqlparser.workspace = true +sqlparser_derive = "0.1" diff --git a/src/datatypes/src/error.rs b/src/datatypes/src/error.rs index 5a255dc0a644..177982dfb0c6 100644 --- a/src/datatypes/src/error.rs +++ b/src/datatypes/src/error.rs @@ -205,6 +205,26 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display( + "Invalid data type {} for fulltext in column {}", + data_type, + column_name + ))] + InvalidFulltextDataType { + data_type: String, + column_name: String, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Invalid Fulltext Options, key: {}, value: {}", key, value))] + InvalidFulltextOptions { + key: String, + value: String, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for Error { @@ -222,7 +242,9 @@ impl ErrorExt for Error { | DefaultValueType { .. } | DuplicateMeta { .. } | InvalidTimestampPrecision { .. } - | InvalidPrecisionOrScale { .. } => StatusCode::InvalidArguments, + | InvalidPrecisionOrScale { .. } + | InvalidFulltextDataType { .. } + | InvalidFulltextOptions { .. } => StatusCode::InvalidArguments, ValueExceedsPrecision { .. } | CastType { .. } diff --git a/src/datatypes/src/schema.rs b/src/datatypes/src/schema.rs index 3bb35a595f4a..c2e06433f4b1 100644 --- a/src/datatypes/src/schema.rs +++ b/src/datatypes/src/schema.rs @@ -26,12 +26,15 @@ use snafu::{ensure, ResultExt}; use crate::error::{self, DuplicateColumnSnafu, Error, ProjectArrowSchemaSnafu, Result}; pub use crate::schema::column_schema::{ - ColumnSchema, FulltextAnalyzer, FulltextOptions, Metadata, COMMENT_KEY, FULLTEXT_KEY, - TIME_INDEX_KEY, + ChangeFulltextOptions, ColumnSchema, FulltextAnalyzer, FulltextOptions, Metadata, COMMENT_KEY, + FULLTEXT_KEY, TIME_INDEX_KEY, }; pub use crate::schema::constraint::ColumnDefaultConstraint; pub use crate::schema::raw::RawSchema; +const COLUMN_FULLTEXT_OPT_KEY_ANALYZER: &str = "analyzer"; +const COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE: &str = "case_sensitive"; + /// Key used to store version number of the schema in metadata. pub const VERSION_KEY: &str = "greptime:version"; diff --git a/src/datatypes/src/schema/column_schema.rs b/src/datatypes/src/schema/column_schema.rs index c3cd8b345314..852fe32ff4f6 100644 --- a/src/datatypes/src/schema/column_schema.rs +++ b/src/datatypes/src/schema/column_schema.rs @@ -18,7 +18,9 @@ use std::fmt; use arrow::datatypes::Field; use serde::{Deserialize, Serialize}; use snafu::{ensure, ResultExt}; +use sqlparser_derive::{Visit, VisitMut}; +use super::{COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE}; use crate::data_type::{ConcreteDataType, DataType}; use crate::error::{self, Error, Result}; use crate::schema::constraint::ColumnDefaultConstraint; @@ -255,12 +257,26 @@ impl ColumnSchema { } } - pub fn with_fulltext_options(mut self, options: FulltextOptions) -> Result { + pub fn with_fulltext_options(mut self, options: &FulltextOptions) -> Result { + self.set_fulltext_options(options)?; + Ok(self) + } + + pub fn set_fulltext_options(&mut self, options: &FulltextOptions) -> Result<()> { + if self.data_type != ConcreteDataType::string_datatype() { + return error::InvalidFulltextDataTypeSnafu { + data_type: self.data_type.to_string(), + column_name: self.name.clone(), + } + .fail(); + } + self.metadata.insert( FULLTEXT_KEY.to_string(), - serde_json::to_string(&options).context(error::SerializeSnafu)?, + serde_json::to_string(options).context(error::SerializeSnafu)?, ); - Ok(self) + + Ok(()) } } @@ -333,13 +349,111 @@ pub struct FulltextOptions { } /// Fulltext analyzer. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Visit, VisitMut)] pub enum FulltextAnalyzer { #[default] English, Chinese, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut, Default)] +pub struct ChangeFulltextOptions { + pub enable: Option, + pub analyzer: Option, + pub case_sensitive: Option, +} + +impl fmt::Display for ChangeFulltextOptions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "fulltext options: ")?; + if let Some(enable) = self.enable { + write!(f, "enable={}", enable)?; + } else { + write!(f, "enable=None")?; + } + if let Some(analyzer) = &self.analyzer { + match analyzer { + FulltextAnalyzer::English => { + write!(f, ", analyzer=English")?; + } + FulltextAnalyzer::Chinese => { + write!(f, ", analyzer=Chinese")?; + } + } + } else { + write!(f, ", analyzer=None")?; + } + if let Some(case_sensitive) = self.case_sensitive { + write!(f, ", case_sensitive={}", case_sensitive)?; + } else { + write!(f, ", case_sensitive=None")?; + } + Ok(()) + } +} + +/// Parses the provided options to configure full-text search behavior. +/// +/// This function checks for specific keys in the provided `HashMap`: +/// - `COLUMN_FULLTEXT_OPT_KEY_ANALYZER`: Defines the analyzer to use (e.g., "english", "chinese"). +/// - `COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE`: Defines whether the full-text search should be case-sensitive ("true" or "false"). +/// +/// If the provided options contain valid values for the full-text keys, a configured `ChangeFulltextOptions` +/// object is returned. If the options are invalid or missing, the function returns error. +impl TryFrom> for ChangeFulltextOptions { + type Error = Error; + + fn try_from(options: HashMap) -> Result { + let mut fulltext = ChangeFulltextOptions::default(); + + // Check and parse the "analyzer" option + if let Some(analyzer) = options.get(COLUMN_FULLTEXT_OPT_KEY_ANALYZER) { + match analyzer.to_ascii_lowercase().as_str() { + "english" => { + fulltext.enable = Some(true); + fulltext.analyzer = Some(FulltextAnalyzer::English); + } + "chinese" => { + fulltext.enable = Some(true); + fulltext.analyzer = Some(FulltextAnalyzer::Chinese); + } + _ => { + // If the analyzer is invalid, return None to indicate failure + return error::InvalidFulltextOptionsSnafu { + key: COLUMN_FULLTEXT_OPT_KEY_ANALYZER.to_string(), + value: analyzer.to_string(), + } + .fail(); + } + } + } + + // Check and parse the "case_sensitive" option + if let Some(case_sensitive) = options.get(COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE) { + match case_sensitive.to_ascii_lowercase().as_str() { + "true" => { + fulltext.enable = Some(true); + fulltext.case_sensitive = Some(true); + } + "false" => { + fulltext.enable = Some(true); + fulltext.case_sensitive = Some(false); + } + _ => { + // If case sensitivity is invalid, return None + return error::InvalidFulltextOptionsSnafu { + key: COLUMN_FULLTEXT_OPT_KEY_CASE_SENSITIVE.to_string(), + value: case_sensitive.to_string(), + } + .fail(); + } + } + } + + Ok(fulltext) + } +} + impl fmt::Display for FulltextAnalyzer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/mito2/src/sst/index.rs b/src/mito2/src/sst/index.rs index b73bd0df7ee6..f9a6913c5f65 100644 --- a/src/mito2/src/sst/index.rs +++ b/src/mito2/src/sst/index.rs @@ -349,7 +349,7 @@ mod tests { if with_fulltext { let column_schema = ColumnSchema::new("text", ConcreteDataType::string_datatype(), true) - .with_fulltext_options(FulltextOptions { + .with_fulltext_options(&FulltextOptions { enable: true, ..Default::default() }) diff --git a/src/mito2/src/sst/index/fulltext_index/creator.rs b/src/mito2/src/sst/index/fulltext_index/creator.rs index 416e39d9dd5e..7bcb89fcc3b7 100644 --- a/src/mito2/src/sst/index/fulltext_index/creator.rs +++ b/src/mito2/src/sst/index/fulltext_index/creator.rs @@ -329,7 +329,7 @@ mod tests { ConcreteDataType::string_datatype(), true, ) - .with_fulltext_options(FulltextOptions { + .with_fulltext_options(&FulltextOptions { enable: true, analyzer: FulltextAnalyzer::English, case_sensitive: true, @@ -344,7 +344,7 @@ mod tests { ConcreteDataType::string_datatype(), true, ) - .with_fulltext_options(FulltextOptions { + .with_fulltext_options(&FulltextOptions { enable: true, analyzer: FulltextAnalyzer::English, case_sensitive: false, @@ -359,7 +359,7 @@ mod tests { ConcreteDataType::string_datatype(), true, ) - .with_fulltext_options(FulltextOptions { + .with_fulltext_options(&FulltextOptions { enable: true, analyzer: FulltextAnalyzer::Chinese, case_sensitive: false, diff --git a/src/operator/src/expr_factory.rs b/src/operator/src/expr_factory.rs index e9be2e712fab..bea41810b534 100644 --- a/src/operator/src/expr_factory.rs +++ b/src/operator/src/expr_factory.rs @@ -18,15 +18,15 @@ use api::helper::ColumnDataTypeWrapper; use api::v1::alter_expr::Kind; use api::v1::column_def::options_from_column_schema; use api::v1::{ - AddColumn, AddColumns, AlterExpr, ChangeColumnType, ChangeColumnTypes, ColumnDataType, - ColumnDataTypeExtension, CreateFlowExpr, CreateTableExpr, CreateViewExpr, DropColumn, - DropColumns, ExpireAfter, RenameTable, SemanticType, TableName, + AddColumn, AddColumns, AlterExpr, Analyzer, ChangeColumnType, ChangeColumnTypes, + ChangeFulltext, ColumnDataType, ColumnDataTypeExtension, CreateFlowExpr, CreateTableExpr, + CreateViewExpr, DropColumn, DropColumns, ExpireAfter, RenameTable, SemanticType, TableName, }; use common_error::ext::BoxedError; use common_grpc_expr::util::ColumnExpr; use common_time::Timezone; use datafusion::sql::planner::object_name_to_table_reference; -use datatypes::schema::{ColumnSchema, COMMENT_KEY}; +use datatypes::schema::{ColumnSchema, FulltextAnalyzer, COMMENT_KEY}; use file_engine::FileOptions; use query::sql::{ check_file_to_table_schema_compatibility, file_column_schemas_to_table, @@ -483,6 +483,19 @@ pub(crate) fn to_alter_expr( AlterTableOperation::RenameTable { new_table_name } => Kind::RenameTable(RenameTable { new_table_name: new_table_name.to_string(), }), + AlterTableOperation::AlterColumnFulltext { + column_name, + options, + } => Kind::ChangeFulltext(ChangeFulltext { + column_name: column_name.value.to_string(), + enable: options.enable, + analyzer: match options.analyzer { + Some(FulltextAnalyzer::Chinese) => Some(Analyzer::Chinese as i32), + Some(FulltextAnalyzer::English) => Some(Analyzer::English as i32), + None => None, + }, + case_sensitive: options.case_sensitive, + }), }; Ok(AlterExpr { diff --git a/src/query/src/sql/show_create_table.rs b/src/query/src/sql/show_create_table.rs index 2c560bd36013..35b61503808d 100644 --- a/src/query/src/sql/show_create_table.rs +++ b/src/query/src/sql/show_create_table.rs @@ -222,7 +222,7 @@ mod tests { ColumnSchema::new("cpu", ConcreteDataType::float64_datatype(), true), ColumnSchema::new("disk", ConcreteDataType::float32_datatype(), true), ColumnSchema::new("msg", ConcreteDataType::string_datatype(), true) - .with_fulltext_options(FulltextOptions { + .with_fulltext_options(&FulltextOptions { enable: true, ..Default::default() }) diff --git a/src/sql/src/parsers/alter_parser.rs b/src/sql/src/parsers/alter_parser.rs index 0473ef52cee5..e45fed7fe5a4 100644 --- a/src/sql/src/parsers/alter_parser.rs +++ b/src/sql/src/parsers/alter_parser.rs @@ -1,5 +1,3 @@ -// Copyright 2023 Greptime Team -// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,7 +10,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashMap; + use common_query::AddColumnLocation; +use datatypes::schema::ChangeFulltextOptions; use snafu::ResultExt; use sqlparser::keywords::Keyword; use sqlparser::parser::ParserError; @@ -22,6 +23,7 @@ use crate::error::{self, Result}; use crate::parser::ParserContext; use crate::statements::alter::{AlterTable, AlterTableOperation}; use crate::statements::statement::Statement; +use crate::util::parse_option_string; impl<'a> ParserContext<'a> { pub(crate) fn parse_alter(&mut self) -> Result { @@ -74,13 +76,45 @@ impl<'a> ParserContext<'a> { ))); } } else if self.consume_token("MODIFY") { - let _ = self.parser.parse_keyword(Keyword::COLUMN); + if !self.parser.parse_keyword(Keyword::COLUMN) { + return Err(ParserError::ParserError(format!( + "expect keyword COLUMN after ALTER TABLE MODIFY, found {}", + self.parser.peek_token() + ))); + } let column_name = Self::canonicalize_identifier(self.parser.parse_identifier(false)?); - let target_type = self.parser.parse_data_type()?; - AlterTableOperation::ChangeColumnType { - column_name, - target_type, + if self.parser.parse_keyword(Keyword::SET) { + let _ = self.parser.parse_keyword(Keyword::FULLTEXT); + + let options = self + .parser + .parse_options(Keyword::WITH)? + .into_iter() + .map(parse_option_string) + .collect::>>() + .unwrap(); + + let fulltext_options: ChangeFulltextOptions = match options.try_into() { + Ok(options) => options, + Err(_) => { + return Err(ParserError::ParserError( + "failed to parse fulltext options".to_string(), + )) + } + }; + + AlterTableOperation::AlterColumnFulltext { + column_name, + options: fulltext_options, + } + } else { + let target_type = self.parser.parse_data_type()?; + + AlterTableOperation::ChangeColumnType { + column_name, + target_type, + } } } else if self.parser.parse_keyword(Keyword::RENAME) { let new_table_name_obj_raw = self.parse_object_name()?; @@ -96,7 +130,7 @@ impl<'a> ParserContext<'a> { AlterTableOperation::RenameTable { new_table_name } } else { return Err(ParserError::ParserError(format!( - "expect keyword ADD or DROP or MODIFY or RENAME after ALTER TABLE, found {}", + "expect keyword ADD or DROP or MODIFY or RENAME or SET after ALTER TABLE, found {}", self.parser.peek_token() ))); }; @@ -109,6 +143,7 @@ mod tests { use std::assert_matches::assert_matches; use common_error::ext::ErrorExt; + use datatypes::schema::FulltextAnalyzer; use sqlparser::ast::{ColumnOption, DataType}; use super::*; @@ -406,4 +441,44 @@ mod tests { _ => unreachable!(), } } + + #[test] + fn test_parse_alter_change_fulltext() { + let sql = "ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'true');"; + let mut result = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + assert_eq!(1, result.len()); + + let statement = result.remove(0); + assert_matches!(statement, Statement::Alter { .. }); + match statement { + Statement::Alter(alter_table) => { + assert_eq!("test", alter_table.table_name().0[0].value); + + let alter_operation = alter_table.alter_operation(); + assert_matches!( + alter_operation, + AlterTableOperation::AlterColumnFulltext { .. } + ); + match alter_operation { + AlterTableOperation::AlterColumnFulltext { + column_name, + options, + } => { + assert_eq!(column_name.value, r#"message"#); + assert!(options.analyzer.is_some()); + assert_eq!( + options.analyzer.as_ref().unwrap().clone(), + FulltextAnalyzer::Chinese + ); + assert!(options.case_sensitive.is_some()); + assert_eq!(options.case_sensitive.unwrap(), true); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + } } diff --git a/src/sql/src/statements.rs b/src/sql/src/statements.rs index 7568fcb24f2a..7e2fcd36f161 100644 --- a/src/sql/src/statements.rs +++ b/src/sql/src/statements.rs @@ -463,7 +463,7 @@ pub fn column_to_schema( if let Some(options) = column.extensions.build_fulltext_options()? { column_schema = column_schema - .with_fulltext_options(options) + .with_fulltext_options(&options) .context(SetFulltextOptionSnafu)?; } diff --git a/src/sql/src/statements/alter.rs b/src/sql/src/statements/alter.rs index 5d679549a187..eed8fc1f9cc7 100644 --- a/src/sql/src/statements/alter.rs +++ b/src/sql/src/statements/alter.rs @@ -15,6 +15,7 @@ use std::fmt::{Debug, Display}; use common_query::AddColumnLocation; +use datatypes::schema::ChangeFulltextOptions; use sqlparser::ast::{ColumnDef, DataType, Ident, ObjectName, TableConstraint}; use sqlparser_derive::{Visit, VisitMut}; @@ -71,6 +72,11 @@ pub enum AlterTableOperation { DropColumn { name: Ident }, /// `RENAME ` RenameTable { new_table_name: String }, + /// `SET COLUMN FULLTEXT WITH ` + AlterColumnFulltext { + column_name: Ident, + options: ChangeFulltextOptions, + }, } impl Display for AlterTableOperation { @@ -97,6 +103,12 @@ impl Display for AlterTableOperation { } => { write!(f, r#"MODIFY COLUMN {column_name} {target_type}"#) } + AlterTableOperation::AlterColumnFulltext { + column_name, + options, + } => { + write!(f, r#"SET COLUMN {column_name} FULLTEXT WITH {:?}"#, options) + } } } } diff --git a/src/store-api/src/metadata.rs b/src/store-api/src/metadata.rs index a94879675e5b..c4f133ef2e97 100644 --- a/src/store-api/src/metadata.rs +++ b/src/store-api/src/metadata.rs @@ -28,7 +28,7 @@ use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; use common_macro::stack_trace_debug; use datatypes::arrow::datatypes::FieldRef; -use datatypes::schema::{ColumnSchema, Schema, SchemaRef}; +use datatypes::schema::{ChangeFulltextOptions, ColumnSchema, FulltextOptions, Schema, SchemaRef}; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize}; use snafu::{ensure, Location, OptionExt, ResultExt, Snafu}; @@ -523,6 +523,10 @@ impl RegionMetadataBuilder { AlterKind::AddColumns { columns } => self.add_columns(columns)?, AlterKind::DropColumns { names } => self.drop_columns(&names), AlterKind::ChangeColumnTypes { columns } => self.change_column_types(columns), + AlterKind::ChangeColumnFulltext { + column_name, + options, + } => self.change_column_fulltext(column_name, options)?, } Ok(self) } @@ -622,6 +626,41 @@ impl RegionMetadataBuilder { } } } + + /// Changes column fulltext option. + fn change_column_fulltext( + &mut self, + column_name: String, + options: ChangeFulltextOptions, + ) -> Result<()> { + for column_meta in self.column_metadatas.iter_mut() { + if column_name.eq(&column_meta.column_schema.name) { + let mut fulltext = if let Ok(Some(f)) = column_meta.column_schema.fulltext_options() + { + f + } else { + FulltextOptions::default() + }; + + if let Some(enable) = options.enable { + fulltext.enable = enable; + } + if let Some(ref analyzer) = options.analyzer { + fulltext.analyzer = analyzer.clone(); + } + if let Some(case_sensitive) = options.case_sensitive { + fulltext.case_sensitive = case_sensitive; + } + + column_meta + .column_schema + .set_fulltext_options(&fulltext) + .expect("set fulltext"); + } + } + + Ok(()) + } } /// Fields skipped in serialization. @@ -738,6 +777,20 @@ pub enum MetadataError { #[snafu(implicit)] location: Location, }, + + #[snafu(display("Invalid fulltext options"))] + InvalidFulltextOptions { + source: datatypes::error::Error, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Invalid fulltext options in proto"))] + InvalidFulltextOptionsProto { + source: api::error::Error, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for MetadataError { diff --git a/src/store-api/src/region_request.rs b/src/store-api/src/region_request.rs index 1452fcbe6125..95304a144838 100644 --- a/src/store-api/src/region_request.rs +++ b/src/store-api/src/region_request.rs @@ -22,16 +22,17 @@ use api::v1::region::{ CompactRequest, CreateRequest, CreateRequests, DeleteRequests, DropRequest, DropRequests, FlushRequest, InsertRequests, OpenRequest, TruncateRequest, }; -use api::v1::{self, Rows, SemanticType}; +use api::v1::{self, column_def, Rows, SemanticType}; pub use common_base::AffectedRows; use datatypes::data_type::ConcreteDataType; -use snafu::{ensure, OptionExt}; +use datatypes::schema::ChangeFulltextOptions; +use snafu::{ensure, OptionExt, ResultExt}; use strum::IntoStaticStr; use crate::logstore::entry; use crate::metadata::{ - ColumnMetadata, InvalidRawRegionRequestSnafu, InvalidRegionRequestSnafu, MetadataError, - RegionMetadata, Result, + ColumnMetadata, InvalidFulltextOptionsProtoSnafu, InvalidRawRegionRequestSnafu, + InvalidRegionRequestSnafu, MetadataError, RegionMetadata, Result, }; use crate::path_utils::region_dir; use crate::storage::{ColumnId, RegionId, ScanRequest}; @@ -389,6 +390,13 @@ pub enum AlterKind { /// Columns to change. columns: Vec, }, + /// Change columns fulltext from the region, only fields are allowed to change. + ChangeColumnFulltext { + /// Name of column to change. + column_name: String, + /// Fulltext options. + options: ChangeFulltextOptions, + }, } impl AlterKind { @@ -412,6 +420,9 @@ impl AlterKind { col_to_change.validate(metadata)?; } } + AlterKind::ChangeColumnFulltext { column_name, .. } => { + Self::validate_column_fulltext_to_alter(metadata, column_name)? + } } Ok(()) } @@ -429,6 +440,9 @@ impl AlterKind { AlterKind::ChangeColumnTypes { columns } => columns .iter() .any(|col_to_change| col_to_change.need_alter(metadata)), + AlterKind::ChangeColumnFulltext { column_name, .. } => { + metadata.column_by_name(column_name).is_some() + } } } @@ -446,6 +460,22 @@ impl AlterKind { ); Ok(()) } + + /// Returns an error if the column to change fulltext is invalid. + pub fn validate_column_fulltext_to_alter( + metadata: &RegionMetadata, + column_name: &str, + ) -> Result<()> { + // Ensure the column exists in the region metadata, otherwise return an error + metadata + .column_by_name(column_name) + .with_context(|| InvalidRegionRequestSnafu { + region_id: metadata.region_id, + err: format!("column {} not found", column_name), + })?; + + Ok(()) + } } impl TryFrom for AlterKind { @@ -473,6 +503,15 @@ impl TryFrom for AlterKind { let names = x.drop_columns.into_iter().map(|x| x.name).collect(); AlterKind::DropColumns { names } } + alter_request::Kind::ChangeFulltext(x) => AlterKind::ChangeColumnFulltext { + column_name: x.column_name, + options: ChangeFulltextOptions { + enable: x.enable, + analyzer: column_def::try_as_fulltext_option(x.analyzer) + .context(InvalidFulltextOptionsProtoSnafu)?, + case_sensitive: x.case_sensitive, + }, + }, }; Ok(alter_kind) diff --git a/src/table/src/error.rs b/src/table/src/error.rs index 9b633bb5bb98..977c2aa415be 100644 --- a/src/table/src/error.rs +++ b/src/table/src/error.rs @@ -137,6 +137,21 @@ pub enum Error { #[snafu(implicit)] location: Location, }, + + #[snafu(display("column {} has invalid colum type to set fulltext", column_name))] + InvalidFulltextColumnType { + column_name: String, + source: datatypes::error::Error, + #[snafu(implicit)] + location: Location, + }, + + #[snafu(display("Invalid fulltext options"))] + InvalidFulltextOptions { + source: datatypes::error::Error, + #[snafu(implicit)] + location: Location, + }, } impl ErrorExt for Error { @@ -148,11 +163,13 @@ impl ErrorExt for Error { } Error::RemoveColumnInIndex { .. } | Error::BuildColumnDescriptor { .. } - | Error::InvalidAlterRequest { .. } => StatusCode::InvalidArguments, + | Error::InvalidAlterRequest { .. } + | Error::InvalidFulltextColumnType { .. } => StatusCode::InvalidArguments, Error::TablesRecordBatch { .. } => StatusCode::Unexpected, Error::ColumnExists { .. } => StatusCode::TableColumnExists, Error::SchemaBuild { source, .. } => source.status_code(), Error::TableOperation { source } => source.status_code(), + Error::InvalidFulltextOptions { source, .. } => source.status_code(), Error::ColumnNotExists { .. } => StatusCode::TableColumnNotFound, Error::Unsupported { .. } => StatusCode::Unsupported, Error::ParseTableOption { .. } => StatusCode::InvalidArguments, diff --git a/src/table/src/metadata.rs b/src/table/src/metadata.rs index d01eb82a68c4..08affb5907db 100644 --- a/src/table/src/metadata.rs +++ b/src/table/src/metadata.rs @@ -1,5 +1,3 @@ -// Copyright 2023 Greptime Team -// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -20,7 +18,10 @@ use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use common_query::AddColumnLocation; use datafusion_expr::TableProviderFilterPushDown; pub use datatypes::error::{Error as ConvertError, Result as ConvertResult}; -use datatypes::schema::{ColumnSchema, RawSchema, Schema, SchemaBuilder, SchemaRef}; +use datatypes::schema::{ + ChangeFulltextOptions, ColumnSchema, FulltextOptions, RawSchema, Schema, SchemaBuilder, + SchemaRef, +}; use derive_builder::Builder; use serde::{Deserialize, Serialize}; use snafu::{ensure, OptionExt, ResultExt}; @@ -208,6 +209,10 @@ impl TableMeta { .next_column_id(self.next_column_id); Ok(meta_builder) } + AlterKind::ChangeFulltext { + column_name, + options, + } => self.change_column_fulltext(table_name, column_name, options.clone()), } } @@ -588,6 +593,82 @@ impl TableMeta { Ok(meta_builder) } + fn change_column_fulltext( + &self, + table_name: &str, + column_name: &String, + options: ChangeFulltextOptions, + ) -> Result { + let table_schema = &self.schema; + let mut meta_builder = self.new_meta_builder(); + + let column = &table_schema + .column_schema_by_name(column_name) + .with_context(|| error::ColumnNotExistsSnafu { + column_name, + table_name, + })?; + + let mut fulltext = if let Ok(Some(f)) = column.fulltext_options() { + f + } else { + FulltextOptions::default() + }; + + if let Some(enable) = options.enable { + fulltext.enable = enable; + } + if let Some(analyzer) = options.analyzer { + fulltext.analyzer = analyzer; + } + if let Some(case_sensitive) = options.case_sensitive { + fulltext.case_sensitive = case_sensitive; + } + + let columns: Result> = table_schema + .column_schemas() + .iter() + .cloned() + .map(|mut column| { + if column.name == *column_name { + column = column + .clone() + .with_fulltext_options(&fulltext) + .context(error::InvalidFulltextColumnTypeSnafu { column_name })?; + } + Ok(column) + }) + .collect(); + + match columns { + Ok(columns) => { + let mut builder = SchemaBuilder::try_from_columns(columns) + .with_context(|_| error::SchemaBuildSnafu { + msg: format!( + "Failed to convert column schemas into schema for table {table_name}" + ), + })? + .version(table_schema.version() + 1); + + for (k, v) in table_schema.metadata().iter() { + builder = builder.add_metadata(k, v); + } + let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu { + msg: format!( + "Table {table_name} cannot change datatype with columns {column_name}" + ), + })?; + + let _ = meta_builder + .schema(Arc::new(new_schema)) + .primary_key_indices(self.primary_key_indices.clone()); + + Ok(meta_builder) + } + Err(e) => Err(e), + } + } + /// Split requests into different groups using column location info. fn split_requests_by_column_location<'a>( &self, @@ -860,7 +941,7 @@ mod tests { use common_error::ext::ErrorExt; use common_error::status_code::StatusCode; use datatypes::data_type::ConcreteDataType; - use datatypes::schema::{ColumnSchema, Schema, SchemaBuilder}; + use datatypes::schema::{ColumnSchema, FulltextAnalyzer, Schema, SchemaBuilder}; use super::*; @@ -882,6 +963,24 @@ mod tests { .unwrap() } + fn new_test_schema2() -> Schema { + let column_schemas = vec![ + ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true), + ColumnSchema::new( + "ts", + ConcreteDataType::timestamp_millisecond_datatype(), + false, + ) + .with_time_index(true), + ColumnSchema::new("col2", ConcreteDataType::int32_datatype(), true), + ]; + SchemaBuilder::try_from(column_schemas) + .unwrap() + .version(123) + .build() + .unwrap() + } + #[test] fn test_raw_convert() { let schema = Arc::new(new_test_schema()); @@ -1300,4 +1399,42 @@ mod tests { assert_eq!(&[0, 1], &new_meta.primary_key_indices[..]); assert_eq!(&[2, 3, 4], &new_meta.value_indices[..]); } + + #[test] + fn test_change_column_fulltext() { + let schema = Arc::new(new_test_schema2()); + let meta = TableMetaBuilder::default() + .schema(schema) + .primary_key_indices(vec![0]) + .engine("engine") + .next_column_id(3) + .build() + .unwrap(); + + let alter_kind = AlterKind::ChangeFulltext { + column_name: "col1".to_string(), + options: ChangeFulltextOptions { + enable: Some(true), + analyzer: Some(FulltextAnalyzer::English), + case_sensitive: None, + }, + }; + assert!(meta + .builder_with_alter_kind("my_table", &alter_kind, false) + .is_ok()); + + let alter_kind = AlterKind::ChangeFulltext { + column_name: "col1".to_string(), + options: ChangeFulltextOptions { + enable: None, + analyzer: None, + case_sensitive: Some(false), + }, + }; + let err = meta + .builder_with_alter_kind("my_table", &alter_kind, false) + .err() + .unwrap(); + assert_eq!(StatusCode::InvalidArguments, err.status_code()); + } } diff --git a/src/table/src/requests.rs b/src/table/src/requests.rs index a00b25eacb13..9be6e4ee8cfd 100644 --- a/src/table/src/requests.rs +++ b/src/table/src/requests.rs @@ -25,7 +25,7 @@ use common_query::AddColumnLocation; use common_time::range::TimestampRange; use datatypes::data_type::ConcreteDataType; use datatypes::prelude::VectorRef; -use datatypes::schema::ColumnSchema; +use datatypes::schema::{ChangeFulltextOptions, ColumnSchema}; use greptime_proto::v1::region::compact_request; use serde::{Deserialize, Serialize}; use store_api::metric_engine_consts::{LOGICAL_TABLE_METADATA_KEY, PHYSICAL_TABLE_METADATA_KEY}; @@ -211,6 +211,10 @@ pub enum AlterKind { RenameTable { new_table_name: String, }, + ChangeFulltext { + column_name: String, + options: ChangeFulltextOptions, + }, } #[derive(Debug)] diff --git a/tests/cases/standalone/common/alter/change_fulltext_options.result b/tests/cases/standalone/common/alter/change_fulltext_options.result new file mode 100644 index 000000000000..b344f61fb5d5 --- /dev/null +++ b/tests/cases/standalone/common/alter/change_fulltext_options.result @@ -0,0 +1,199 @@ +CREATE TABLE `test` ( + `message` STRING FULLTEXT, + `time` TIMESTAMP TIME INDEX, +) WITH ( + append_mode = 'true' +); + +Affected Rows: 0 + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'true'); + +Affected Rows: 0 + +-- SQLNESS ARG restart=true +SHOW CREATE TABLE test; + ++-------+---------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+---------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'true'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+---------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'false'); + +Affected Rows: 0 + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'false'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'English', case_sensitive = 'true'); + +Affected Rows: 0 + +SHOW CREATE TABLE test; + ++-------+---------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+---------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'English', case_sensitive = 'true'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+---------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'); + +Affected Rows: 0 + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'English', case_sensitive = 'yes'); + +Error: 2000(InvalidSyntax), sql parser error: failed to parse fulltext options + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'Chinglish', case_sensitive = 'false'); + +Error: 2000(InvalidSyntax), sql parser error: failed to parse fulltext options + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN time SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'false'); + +Error: 1004(InvalidArguments), Invalid data type TimestampMillisecond for fulltext in column time + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------+ + +ALTER TABLE test MODIFY COLUMN time SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'no'); + +Error: 2000(InvalidSyntax), sql parser error: failed to parse fulltext options + +SHOW CREATE TABLE test; + ++-------+----------------------------------------------------------------------------------------+ +| Table | Create Table | ++-------+----------------------------------------------------------------------------------------+ +| test | CREATE TABLE IF NOT EXISTS "test" ( | +| | "message" STRING NULL FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'), | +| | "time" TIMESTAMP(3) NOT NULL, | +| | TIME INDEX ("time") | +| | ) | +| | | +| | ENGINE=mito | +| | WITH( | +| | append_mode = 'true' | +| | ) | ++-------+----------------------------------------------------------------------------------------+ + +DROP TABLE test; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/alter/change_fulltext_options.sql b/tests/cases/standalone/common/alter/change_fulltext_options.sql new file mode 100644 index 000000000000..cfc39447725f --- /dev/null +++ b/tests/cases/standalone/common/alter/change_fulltext_options.sql @@ -0,0 +1,43 @@ +CREATE TABLE `test` ( + `message` STRING FULLTEXT, + `time` TIMESTAMP TIME INDEX, +) WITH ( + append_mode = 'true' +); + +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'true'); + +-- SQLNESS ARG restart=true +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'false'); + +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'English', case_sensitive = 'true'); + +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'English', case_sensitive = 'false'); + +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'English', case_sensitive = 'yes'); + +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN message SET FULLTEXT WITH(analyzer = 'Chinglish', case_sensitive = 'false'); + +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN time SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'false'); + +SHOW CREATE TABLE test; + +ALTER TABLE test MODIFY COLUMN time SET FULLTEXT WITH(analyzer = 'Chinese', case_sensitive = 'no'); + +SHOW CREATE TABLE test; + +DROP TABLE test;