Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Alter column fulltext option #4637

Closed
wants to merge 20 commits into from
Closed
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "2a8c2dfb7447b7fe5f0b8f3df868f041600caec2" }
humantime = "2.1"
humantime-serde = "1.1"
itertools = "0.10"
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: No need to change this. The comments have been removed in the latest main.

rskafka = { git = "https://github.com/WenyXu/rskafka.git", rev = "940c6030012c5b746fad819fb72e3325b26e39de", features = [
"transport-tls",
] }
Expand Down
2 changes: 1 addition & 1 deletion src/api/src/v1/column_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,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,
Expand Down
19 changes: 16 additions & 3 deletions src/common/grpc-expr/src/alter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ use api::helper::ColumnDataTypeWrapper;
use api::v1::add_column_location::LocationType;
use api::v1::alter_expr::Kind;
use api::v1::{
column_def, AddColumnLocation as Location, AlterExpr, ChangeColumnTypes, CreateTableExpr,
DropColumns, RenameTable, SemanticType,
column_def, AddColumnLocation as Location, AlterExpr, ChangeColumnTypes, ChangeFulltext,
CreateTableExpr, DropColumns, RenameTable, SemanticType,
};
use common_query::AddColumnLocation;
use datatypes::schema::{ColumnSchema, RawSchema};
use datatypes::schema::{ChangeFulltextOptions, ColumnSchema, RawSchema};
use snafu::{ensure, OptionExt, ResultExt};
use table::metadata::TableId;
use table::requests::{AddColumnRequest, AlterKind, AlterTableRequest, ChangeColumnTypeRequest};
Expand Down Expand Up @@ -92,6 +92,19 @@ pub fn alter_expr_to_request(table_id: TableId, expr: AlterExpr) -> Result<Alter
Kind::RenameTable(RenameTable { new_table_name }) => {
AlterKind::RenameTable { new_table_name }
}
Kind::ChangeFulltext(ChangeFulltext {
column_name,
enable,
analyzer,
case_sensitive,
}) => AlterKind::ChangeFulltext {
column_name,
options: ChangeFulltextOptions {
enable,
analyzer,
case_sensitive,
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a new function under api::v1::column_def

/// Tries to construct a `ChangeFulltextOptions` from the given `ChangeFulltext`.
pub fn try_as_change_fulltext_options(
    ChangeFulltext {
        column_name,
        enable,
        analyzer,
        case_sensitive,
    }: &ChangeFulltext,
) -> Result<ChangeFulltextOptions> {
    let analyer = analyzer
        .map(|a| Analyzer::try_from(a))
        .transpose()
        .context(error::DecodeProto)?;
    Ok(ChangeFulltextOptions {
        enable: *enable,
        analyzer: analyzer,
        case_sensitive: *case_sensitive,
    })
}

and use it like

        }) => AlterKind::ChangeFulltext {
             column_name,
             options: column_def::try_as_change_fulltext_options(&change)?.context(...)

},
};

let request = AlterTableRequest {
Expand Down
11 changes: 8 additions & 3 deletions src/common/grpc-expr/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ pub enum Error {
InvalidFulltextColumnType {
column_name: String,
column_type: ColumnDataType,
},

#[snafu(display("Invalid fulltext options"))]
InvalidFulltextOptions {
source: datatypes::error::Error,
#[snafu(implicit)]
location: Location,
},
Expand All @@ -145,9 +150,9 @@ impl ErrorExt for Error {
StatusCode::InvalidArguments
}

Error::UnknownColumnDataType { .. } | Error::InvalidFulltextColumnType { .. } => {
StatusCode::InvalidArguments
}
Error::UnknownColumnDataType { .. }
| Error::InvalidFulltextOptions { .. }
| Error::InvalidFulltextColumnType { .. } => StatusCode::InvalidArguments,
}
}

Expand Down
1 change: 1 addition & 0 deletions src/common/meta/src/ddl/alter_table/region_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ fn create_proto_alter_kind(
})))
}
Kind::RenameTable(_) => Ok(None),
Kind::ChangeFulltext(x) => Ok(Some(alter_request::Kind::ChangeFulltext(x.clone()))),
}
}

Expand Down
1 change: 1 addition & 0 deletions src/common/meta/src/ddl/alter_table/update_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl AlterTableProcedure {
new_info.name = new_table_name.to_string();
}
AlterKind::DropColumns { .. } | AlterKind::ChangeColumnTypes { .. } => {}
AlterKind::ChangeFulltext { .. } => {}
}

Ok(new_info)
Expand Down
2 changes: 2 additions & 0 deletions src/datatypes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ paste = "1.0"
serde.workspace = true
serde_json.workspace = true
snafu.workspace = true
sqlparser.workspace = true
sqlparser_derive = "0.1"
19 changes: 18 additions & 1 deletion src/datatypes/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,21 @@ pub enum Error {
#[snafu(implicit)]
location: Location,
},

#[snafu(display("Invalid data type {} for fulltext", data_type))]
InvalidFulltextDataType {
data_type: 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 {
Expand All @@ -222,7 +237,9 @@ impl ErrorExt for Error {
| DefaultValueType { .. }
| DuplicateMeta { .. }
| InvalidTimestampPrecision { .. }
| InvalidPrecisionOrScale { .. } => StatusCode::InvalidArguments,
| InvalidPrecisionOrScale { .. }
| InvalidFulltextDataType { .. }
| InvalidFulltextOptions { .. } => StatusCode::InvalidArguments,

ValueExceedsPrecision { .. }
| CastType { .. }
Expand Down
7 changes: 5 additions & 2 deletions src/datatypes/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
136 changes: 133 additions & 3 deletions src/datatypes/src/schema/column_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -255,12 +257,25 @@ impl ColumnSchema {
}
}

pub fn with_fulltext_options(mut self, options: FulltextOptions) -> Result<Self> {
pub fn with_fulltext_options(mut self, options: &FulltextOptions) -> Result<Self> {
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add column name in error

data_type: self.data_type.to_string(),
}
.fail();
}

self.metadata.insert(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you can add a check whether the column type is string: self.data_type == ConcreteDataType::string_datatype(), and also change the with_fulltext_options above

FULLTEXT_KEY.to_string(),
serde_json::to_string(&options).context(error::SerializeSnafu)?,
serde_json::to_string(options).context(error::SerializeSnafu)?,
);
Ok(self)

Ok(())
}
}

Expand Down Expand Up @@ -340,6 +355,121 @@ pub enum FulltextAnalyzer {
Chinese,
}

impl TryFrom<i32> for FulltextAnalyzer {
type Error = Error;

fn try_from(value: i32) -> Result<Self> {
match value {
0 => Ok(FulltextAnalyzer::English),
1 => Ok(FulltextAnalyzer::Chinese),
_ => Err(error::InvalidFulltextOptionsSnafu {
key: "analyzer".to_string(),
value: value.to_string(),
}
.build()),
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
impl TryFrom<i32> for FulltextAnalyzer {
type Error = Error;
fn try_from(value: i32) -> Result<Self> {
match value {
0 => Ok(FulltextAnalyzer::English),
1 => Ok(FulltextAnalyzer::Chinese),
_ => Err(error::InvalidFulltextOptionsSnafu {
key: "analyzer".to_string(),
value: value.to_string(),
}
.build()),
}
}
}


#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut, Default)]
pub struct ChangeFulltextOptions {
pub enable: Option<bool>,
pub analyzer: Option<i32>,
pub case_sensitive: Option<bool>,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut, Default)]
pub struct ChangeFulltextOptions {
pub enable: Option<bool>,
pub analyzer: Option<i32>,
pub case_sensitive: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Visit, VisitMut, Default)]
pub struct ChangeFulltextOptions {
pub enable: Option<bool>,
pub analyzer: Option<FulltextAnalyzer>,
pub case_sensitive: Option<bool>,
}


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 {
if analyzer == 0 {
write!(f, ", analyzer=English")?;
} else {
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(())
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 {
if analyzer == 0 {
write!(f, ", analyzer=English")?;
} else {
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(())
}
}
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(())
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, We can remove this


/// 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<HashMap<String, String>> for ChangeFulltextOptions {
type Error = Error;

fn try_from(options: HashMap<String, String>) -> Result<Self> {
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(0);
}
"chinese" => {
fulltext.enable = Some(true);
fulltext.analyzer = Some(1);
}
_ => {
// 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)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused code

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used by let fulltext_options: ChangeFulltextOptions = match options.try_into() {...} to extract FulltextOptions from map.


impl fmt::Display for FulltextAnalyzer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand Down
2 changes: 1 addition & 1 deletion src/mito2/src/sst/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
Expand Down
6 changes: 3 additions & 3 deletions src/mito2/src/sst/index/fulltext_index/creator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
Loading