From 536f42e4a262199516113cf45efe7af85a32f994 Mon Sep 17 00:00:00 2001 From: fys <40801205+fengys1996@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:33:41 +0000 Subject: [PATCH] feat(json2): support ALTER syntax for JSON column settings (#9094) feat(json2): support alter syntax for JSON2 columns Signed-off-by: fys This is the commit message #3: --- src/operator/src/expr_helper.rs | 35 ++++++++ src/sql/src/parsers/alter_parser.rs | 79 ++++++++++++++++++- src/sql/src/parsers/create_parser.rs | 1 + src/sql/src/parsers/create_parser/json.rs | 2 +- src/sql/src/statements/alter.rs | 10 ++- src/sql/src/statements/create.rs | 2 +- .../common/types/json/json2_alter.result | 51 ++++++++++++ .../common/types/json/json2_alter.sql | 36 +++++++++ 8 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 tests/cases/standalone/common/types/json/json2_alter.result create mode 100644 tests/cases/standalone/common/types/json/json2_alter.sql diff --git a/src/operator/src/expr_helper.rs b/src/operator/src/expr_helper.rs index 925e062cf5..b28af78d38 100644 --- a/src/operator/src/expr_helper.rs +++ b/src/operator/src/expr_helper.rs @@ -826,9 +826,16 @@ pub(crate) fn to_alter_table_expr( AlterTableOperation::ModifyColumnType { column_name, target_type, + .. } => { let target_type = sql_data_type_to_concrete_data_type(&target_type).context(ParseSqlSnafu)?; + if target_type.is_json2() { + return NotSupportedSnafu { + feat: "ALTER TABLE MODIFY COLUMN to JSON2 type", + } + .fail(); + } let (target_type, target_type_extension) = ColumnDataTypeWrapper::try_from(target_type) .map(|w| w.to_parts()) .context(ColumnDataTypeSnafu)?; @@ -1744,6 +1751,34 @@ SELECT max(c1), min(c2) FROM schema_2.table_2;"; assert!(modify_column_type.target_type_extension.is_none()); } + #[test] + fn test_alter_json2_is_not_supported() { + for sql in [ + "ALTER TABLE monitor MODIFY COLUMN payload JSON2;", + "ALTER TABLE monitor MODIFY COLUMN payload JSON2 (service STRING);", + ] { + let stmt = ParserContext::create_with_dialect( + sql, + &GreptimeDbDialect {}, + ParseOptions::default(), + ) + .unwrap() + .pop() + .unwrap(); + + let Statement::AlterTable(alter_table) = stmt else { + unreachable!() + }; + let err = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap_err(); + + assert!(matches!(err, crate::error::Error::NotSupported { .. })); + assert_eq!( + "Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type", + err.to_string() + ); + } + } + #[test] fn test_to_repartition_request() { let sql = r#" diff --git a/src/sql/src/parsers/alter_parser.rs b/src/sql/src/parsers/alter_parser.rs index 6c71702f5b..d5a89f5407 100644 --- a/src/sql/src/parsers/alter_parser.rs +++ b/src/sql/src/parsers/alter_parser.rs @@ -29,7 +29,7 @@ use sqlparser::tokenizer::{Token, TokenWithSpan}; use crate::ast::ObjectNamePartExt; use crate::error::{self, InvalidColumnOptionSnafu, Result, SetFulltextOptionSnafu}; use crate::parser::ParserContext; -use crate::parsers::create_parser::INVERTED; +use crate::parsers::create_parser::{INVERTED, parse_json2_type_and_options}; use crate::parsers::utils::{ parse_with_options, validate_column_fulltext_create_option, validate_column_skipping_index_create_option, @@ -462,10 +462,19 @@ impl ParserContext<'_> { .context(error::SyntaxSnafu)?; self.parse_alter_table_drop_default(column_name) } else { - let data_type = self.parser.parse_data_type().context(error::SyntaxSnafu)?; + let (data_type, json2_options) = + if let Some(json2) = parse_json2_type_and_options(&mut self.parser)? { + json2 + } else { + ( + self.parser.parse_data_type().context(error::SyntaxSnafu)?, + None, + ) + }; Ok(AlterTableOperation::ModifyColumnType { column_name, target_type: data_type, + json2_options, }) } } @@ -1537,9 +1546,11 @@ ALTER TABLE metrics REPARTITION AlterTableOperation::ModifyColumnType { column_name, target_type, + json2_options, } => { assert_eq!("a", column_name.value); assert_eq!(DataType::String(None), *target_type); + assert!(json2_options.is_none()); } _ => unreachable!(), } @@ -1548,6 +1559,66 @@ ALTER TABLE metrics REPARTITION } } + #[test] + fn test_parse_alter_json2() { + let sql = r#"ALTER TABLE application_logs +MODIFY COLUMN attrs JSON2 ( + max_auto_expanded_paths = 2000, + trace_id STRING, + user.id STRING NOT NULL, + user.name STRING DEFAULT 'anonymous', + request_id STRING INVERTED INDEX +)"#; + let mut statements = + ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default()) + .unwrap(); + + let Statement::AlterTable(alter_table) = statements.remove(0) else { + unreachable!() + }; + let AlterTableOperation::ModifyColumnType { + column_name, + target_type, + json2_options: Some(options), + } = alter_table.alter_operation() + else { + unreachable!() + }; + + assert_eq!("attrs", column_name.value); + assert_eq!("JSON2", target_type.to_string()); + assert_eq!(Some(2000), options.max_auto_expanded_paths); + assert_eq!(4, options.type_hints.len()); + assert_eq!(vec!["user", "id"], options.type_hints[1].path); + assert!(!options.type_hints[1].nullable); + assert!(options.type_hints[2].default.is_some()); + assert!(options.type_hints[3].inverted_index); + + let formatted = alter_table.to_string(); + let reparsed = ParserContext::create_with_dialect( + &formatted, + &GreptimeDbDialect {}, + ParseOptions::default(), + ) + .unwrap(); + assert_eq!(Statement::AlterTable(alter_table), reparsed[0]); + + let mut empty = ParserContext::create_with_dialect( + "ALTER TABLE application_logs MODIFY COLUMN attrs JSON2 ()", + &GreptimeDbDialect {}, + ParseOptions::default(), + ) + .unwrap(); + let Statement::AlterTable(empty) = empty.remove(0) else { + unreachable!() + }; + let AlterTableOperation::ModifyColumnType { json2_options, .. } = empty.alter_operation() + else { + unreachable!() + }; + assert!(json2_options.is_none()); + } + #[test] fn test_parse_alter_change_column_alias_type() { let sql_1 = "ALTER TABLE my_metric_1 MODIFY COLUMN a MediumText"; @@ -1571,9 +1642,11 @@ ALTER TABLE metrics REPARTITION AlterTableOperation::ModifyColumnType { column_name, target_type, + json2_options, } => { assert_eq!("a", column_name.value); assert_eq!(DataType::MediumText, *target_type); + assert!(json2_options.is_none()); } _ => unreachable!(), } @@ -1602,9 +1675,11 @@ ALTER TABLE metrics REPARTITION AlterTableOperation::ModifyColumnType { column_name, target_type, + json2_options, } => { assert_eq!("a", column_name.value); assert!(matches!(target_type, DataType::Timestamp(Some(6), _))); + assert!(json2_options.is_none()); } _ => unreachable!(), } diff --git a/src/sql/src/parsers/create_parser.rs b/src/sql/src/parsers/create_parser.rs index 81ff23d3bc..82dcd949ae 100644 --- a/src/sql/src/parsers/create_parser.rs +++ b/src/sql/src/parsers/create_parser.rs @@ -24,6 +24,7 @@ use datafusion_common::ScalarValue; use datatypes::arrow::datatypes::{DataType as ArrowDataType, IntervalUnit}; use datatypes::data_type::ConcreteDataType; use itertools::Itertools; +pub(crate) use json::parse_json2_type_and_options; pub use json::parse_json2_type_hint_path; use snafu::{OptionExt, ResultExt, ensure}; use sqlparser::ast::{ diff --git a/src/sql/src/parsers/create_parser/json.rs b/src/sql/src/parsers/create_parser/json.rs index 2f8a80c79a..fadc37c6d2 100644 --- a/src/sql/src/parsers/create_parser/json.rs +++ b/src/sql/src/parsers/create_parser/json.rs @@ -49,7 +49,7 @@ pub fn parse_json2_type_hint_path(path: &str) -> Result> { Ok(path) } -pub(super) fn parse_json2_type_and_options( +pub(crate) fn parse_json2_type_and_options( parser: &mut Parser<'_>, ) -> Result)>> { let token = parser.peek_token(); diff --git a/src/sql/src/statements/alter.rs b/src/sql/src/statements/alter.rs index dd1f8e19d2..2f98d8737a 100644 --- a/src/sql/src/statements/alter.rs +++ b/src/sql/src/statements/alter.rs @@ -26,7 +26,7 @@ use sqlparser::ast::{ColumnDef, DataType, Expr, Ident, ObjectName, TableConstrai use sqlparser_derive::{Visit, VisitMut}; use crate::statements::OptionMap; -use crate::statements::create::Partitions; +use crate::statements::create::{Json2Options, Partitions}; #[derive(Debug, Clone, PartialEq, Eq, Visit, VisitMut, Serialize)] pub struct AlterTable { @@ -86,6 +86,7 @@ pub enum AlterTableOperation { ModifyColumnType { column_name: Ident, target_type: DataType, + json2_options: Option, }, /// `SET =
` SetTableOptions { @@ -256,8 +257,13 @@ impl Display for AlterTableOperation { AlterTableOperation::ModifyColumnType { column_name, target_type, + json2_options, } => { - write!(f, r#"MODIFY COLUMN {column_name} {target_type}"#) + write!(f, r#"MODIFY COLUMN {column_name} {target_type}"#)?; + if let Some(options) = json2_options { + write!(f, "{options}")?; + } + Ok(()) } AlterTableOperation::SetTableOptions { options } => { let kvs = options diff --git a/src/sql/src/statements/create.rs b/src/sql/src/statements/create.rs index aa39d42c29..a4736d0a7a 100644 --- a/src/sql/src/statements/create.rs +++ b/src/sql/src/statements/create.rs @@ -141,7 +141,7 @@ pub struct ColumnExtensions { } /// JSON2-specific options represented in the SQL AST. -#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Visit, VisitMut, Default, Serialize)] pub struct Json2Options { /// Maximum number of unhinted JSON2 paths expanded into Arrow fields. pub(crate) max_auto_expanded_paths: Option, diff --git a/tests/cases/standalone/common/types/json/json2_alter.result b/tests/cases/standalone/common/types/json/json2_alter.result new file mode 100644 index 0000000000..5662907d1d --- /dev/null +++ b/tests/cases/standalone/common/types/json/json2_alter.result @@ -0,0 +1,51 @@ +CREATE TABLE application_logs ( + ts TIMESTAMP TIME INDEX, + attrs JSON2 +) WITH ( + 'append_mode' = 'true' +); + +Affected Rows: 0 + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2; + +Error: 1001(Unsupported), Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 (); + +Error: 1001(Unsupported), Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 ( + max_auto_expanded_paths = 2000 + ); + +Error: 1001(Unsupported), Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 ( + trace_id STRING, + user.id STRING NOT NULL, + user.name STRING DEFAULT 'anonymous', + request_id STRING INVERTED INDEX + ); + +Error: 1001(Unsupported), Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 ( + max_auto_expanded_paths = 2000, + trace_id STRING, + user.id STRING NOT NULL, + user.name STRING DEFAULT 'anonymous', + request_id STRING INVERTED INDEX + ); + +Error: 1001(Unsupported), Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type + +DROP TABLE application_logs; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/types/json/json2_alter.sql b/tests/cases/standalone/common/types/json/json2_alter.sql new file mode 100644 index 0000000000..2c17138b86 --- /dev/null +++ b/tests/cases/standalone/common/types/json/json2_alter.sql @@ -0,0 +1,36 @@ +CREATE TABLE application_logs ( + ts TIMESTAMP TIME INDEX, + attrs JSON2 +) WITH ( + 'append_mode' = 'true' +); + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2; + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 (); + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 ( + max_auto_expanded_paths = 2000 + ); + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 ( + trace_id STRING, + user.id STRING NOT NULL, + user.name STRING DEFAULT 'anonymous', + request_id STRING INVERTED INDEX + ); + +ALTER TABLE application_logs + MODIFY COLUMN attrs JSON2 ( + max_auto_expanded_paths = 2000, + trace_id STRING, + user.id STRING NOT NULL, + user.name STRING DEFAULT 'anonymous', + request_id STRING INVERTED INDEX + ); + +DROP TABLE application_logs;