feat(json2): support altering JSON2 settings (#9029)

* feat(sql): support alter syntax for JSON2 columns

Signed-off-by: fys <fengys1996@gmail.com>

* fix(json2): preserve rows on type hint mismatch during compaction

* refactor(json2): simplify alter settings handling

* fix(json2): preserve coerced values during compaction

* chore: remove unnecessary clone

* chor: reduce memory allocations

* fix: cargo clippy

* chore: update greptime-proto to main branch

* refactor(datatypes): unify string handling with other JSON type hints

* fix: cr

---------

Signed-off-by: fys <fengys1996@gmail.com>
This commit is contained in:
fys
2026-09-22 12:56:26 +00:00
committed by GitHub
parent aa36f74feb
commit 045441e3cc
30 changed files with 1602 additions and 231 deletions
+84 -25
View File
@@ -24,9 +24,10 @@ use api::v1::column_def::{options_from_column_schema, try_as_column_schema};
use api::v1::{
AddColumn, AddColumns, AlterDatabaseExpr, AlterTableExpr, Analyzer, ColumnDataType,
ColumnDataTypeExtension, CreateFlowExpr, CreateTableExpr, CreateViewExpr, DropColumn,
DropColumns, DropDefaults, ExpireAfter, FulltextBackend as PbFulltextBackend, ModifyColumnType,
DropColumns, DropDefaults, ExpireAfter, FulltextBackend as PbFulltextBackend,
JsonSettings as PbJsonSettings, JsonTypeHint as PbJsonTypeHint, ModifyColumnType,
ModifyColumnTypes, RenameTable, SemanticType, SetDatabaseOptions, SetDefaults, SetFulltext,
SetIndex, SetIndexes, SetInverted, SetSkipping, SetTableOptions,
SetIndex, SetIndexes, SetInverted, SetJsonSettings, SetSkipping, SetTableOptions,
SkippingIndexType as PbSkippingIndexType, TableName, UnsetDatabaseOptions, UnsetFulltext,
UnsetIndex, UnsetIndexes, UnsetInverted, UnsetSkipping, UnsetTableOptions, set_index,
unset_index,
@@ -36,6 +37,7 @@ 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::json::JsonSettings;
use datatypes::prelude::ConcreteDataType;
use datatypes::schema::{
COLUMN_FULLTEXT_OPT_KEY_ANALYZER, COLUMN_FULLTEXT_OPT_KEY_BACKEND,
@@ -782,6 +784,29 @@ pub(crate) fn to_repartition_request(
})
}
fn json_settings_to_proto(settings: JsonSettings) -> Result<PbJsonSettings> {
let (type_hints, max_auto_expanded_paths) = settings.into_parts();
let type_hints = type_hints
.into_iter()
.map(|hint| {
let (data_type, datatype_extension) = ColumnDataTypeWrapper::try_from(hint.data_type)
.map(|w| w.to_parts())
.context(ColumnDataTypeSnafu)?;
Ok(PbJsonTypeHint {
path: hint.path,
data_type: data_type as i32,
datatype_extension,
})
})
.collect::<Result<Vec<_>>>()?;
Ok(PbJsonSettings {
type_hints,
max_auto_expanded_paths,
})
}
/// Converts a SQL alter table statement into a gRPC alter table expression.
pub(crate) fn to_alter_table_expr(
alter_table: AlterTable,
@@ -830,12 +855,15 @@ pub(crate) fn to_alter_table_expr(
} => {
let target_type =
sql_data_type_to_concrete_data_type(&target_type).context(ParseSqlSnafu)?;
// Currently disallow modify column type to json2.
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)?;
@@ -853,6 +881,20 @@ pub(crate) fn to_alter_table_expr(
}],
})
}
AlterTableOperation::SetJsonSettings {
column_name,
json2_options,
} => {
let settings = match json2_options {
Some(options) => options.build_json_settings().context(ParseSqlSnafu)?,
None => datatypes::json::JsonSettings::new_v2(),
};
AlterTableKind::SetJsonSettings(SetJsonSettings {
column_name: column_name.value,
settings: Some(json_settings_to_proto(settings)?),
})
}
AlterTableOperation::DropColumn { name } => AlterTableKind::DropColumns(DropColumns {
drop_columns: vec![DropColumn {
name: name.value.clone(),
@@ -1752,31 +1794,48 @@ SELECT max(c1), min(c2) FROM schema_2.table_2;";
}
#[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();
fn test_to_alter_set_json_settings_expr() {
let sql = "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();
let Statement::AlterTable(alter_table) = stmt else {
unreachable!()
};
let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
let kind = expr.kind.unwrap();
assert!(matches!(err, crate::error::Error::NotSupported { .. }));
assert_eq!(
"Not supported: ALTER TABLE MODIFY COLUMN to JSON2 type",
err.to_string()
);
}
let AlterTableKind::SetJsonSettings(modify) = kind else {
unreachable!()
};
assert_eq!("payload", modify.column_name);
let settings = modify.settings.as_ref().unwrap();
assert_eq!(Some(100), settings.max_auto_expanded_paths);
assert_eq!(1, settings.type_hints.len());
assert_eq!(["service"], &settings.type_hints[0].path[..]);
let sql = "ALTER TABLE monitor MODIFY COLUMN payload JSON2;";
let stmt =
ParserContext::create_with_dialect(sql, &GreptimeDbDialect {}, ParseOptions::default())
.unwrap()
.pop()
.unwrap();
let Statement::AlterTable(alter_table) = stmt else {
unreachable!()
};
let expr = to_alter_table_expr(alter_table, &QueryContext::arc()).unwrap();
let kind = expr.kind.unwrap();
let AlterTableKind::SetJsonSettings(modify) = kind else {
unreachable!()
};
let settings = modify.settings.as_ref().unwrap();
assert_eq!(Some(100), settings.max_auto_expanded_paths);
assert!(settings.type_hints.is_empty());
}
#[test]