1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- use std:: collections:: HashSet ;
15+ use std:: collections:: HashMap ;
1616use std:: sync:: Arc ;
1717
1818use databend_common_catalog:: catalog:: Catalog ;
@@ -151,28 +151,28 @@ impl ModifyTableColumnInterpreter {
151151 let schema = table. schema ( ) . as_ref ( ) . clone ( ) ;
152152 let table_info = table. get_table_info ( ) ;
153153 let mut new_schema = schema. clone ( ) ;
154- let mut default_expr_binder = DefaultExprBinder :: try_new ( self . ctx . clone ( ) ) ?;
155154 // first check default expr before lock table
156155 for ( field, _comment) in field_and_comments {
157156 if let Some ( ( i, old_field) ) = schema. column_with_name ( & field. name ) {
158157 // if the field has different leaf column numbers, we need drop the old column
159158 // and add a new one to generate new column id. otherwise, leaf column ids will conflict.
160159 if old_field. data_type . num_leaf_columns ( ) != field. data_type . num_leaf_columns ( ) {
161- let _ = new_schema. drop_column ( & field. name ) ;
162- let _ = new_schema. add_column ( field, i) ;
160+ let _ = new_schema. drop_column_unchecked ( & field. name ) ? ;
161+ new_schema. add_column ( field, i) ? ;
163162 } else {
164163 // new field don't have `column_id`, assign field directly will cause `column_id` lost.
165164 new_schema. fields [ i] . data_type = field. data_type . clone ( ) ;
166165 // TODO: support set computed field.
167166 new_schema. fields [ i] . computed_expr = field. computed_expr . clone ( ) ;
168167 }
168+
169169 if let Some ( default_expr) = & field. default_expr {
170170 let default_expr = default_expr. to_string ( ) ;
171171 new_schema. fields [ i] . default_expr = Some ( default_expr) ;
172- let _ = default_expr_binder. get_scalar ( & new_schema. fields [ i] ) ?;
173172 } else {
174173 new_schema. fields [ i] . default_expr = None ;
175174 }
175+
176176 if old_field. data_type != field. data_type {
177177 // Check if this column is referenced by computed columns.
178178 let data_schema = DataSchema :: from ( & new_schema) ;
@@ -256,11 +256,12 @@ impl ModifyTableColumnInterpreter {
256256 return Ok ( PipelineBuildResult :: create ( ) ) ;
257257 }
258258
259- let mut modified_field_indices = HashSet :: new ( ) ;
259+ let mut modified_default_scalars = HashMap :: new ( ) ;
260+ let mut default_expr_binder = DefaultExprBinder :: try_new ( self . ctx . clone ( ) ) ?;
260261 let new_schema_without_computed_fields = new_schema. remove_computed_fields ( ) ;
262+ let format_as_parquet = fuse_table. storage_format_as_parquet ( ) ;
261263 if schema != new_schema {
262264 for ( field, _) in field_and_comments {
263- let field_index = new_schema_without_computed_fields. index_of ( & field. name ) ?;
264265 let old_field = schema. field_with_name ( & field. name ) ?;
265266 let is_alter_column_string_to_binary =
266267 is_string_to_binary ( & old_field. data_type , & field. data_type ) ;
@@ -269,20 +270,25 @@ impl ModifyTableColumnInterpreter {
269270 // 1. alter column from string to binary in parquet or data type not changed.
270271 // 2. default expr and computed expr not changed. Otherwise, we need fill value for
271272 // new added column.
272- if ( ( table . storage_format_as_parquet ( ) && is_alter_column_string_to_binary)
273+ if ( ( format_as_parquet && is_alter_column_string_to_binary)
273274 || old_field. data_type == field. data_type )
274275 && old_field. default_expr == field. default_expr
275276 && old_field. computed_expr == field. computed_expr
276277 {
277278 continue ;
278279 }
279- modified_field_indices. insert ( field_index) ;
280+ let field_index = new_schema_without_computed_fields. index_of ( & field. name ) ?;
281+ let default_scalar = default_expr_binder
282+ . get_scalar ( & new_schema_without_computed_fields. fields [ field_index] ) ?;
283+ modified_default_scalars. insert ( field_index, default_scalar) ;
280284 }
281285 table_info. meta . schema = new_schema. clone ( ) . into ( ) ;
282286 }
283287
284288 // if don't need rebuild table, only update table meta.
285- if modified_field_indices. is_empty ( ) {
289+ if modified_default_scalars. is_empty ( )
290+ || base_snapshot. is_none_or ( |v| v. summary . row_count == 0 )
291+ {
286292 commit_table_meta (
287293 & self . ctx ,
288294 table. as_ref ( ) ,
@@ -295,14 +301,24 @@ impl ModifyTableColumnInterpreter {
295301 return Ok ( PipelineBuildResult :: create ( ) ) ;
296302 }
297303
304+ if fuse_table. change_tracking_enabled ( ) {
305+ // Modifying columns while change tracking is active may break
306+ // the consistency between tracked changes and the current table schema,
307+ // leading to incorrect or incomplete change records.
308+ return Err ( ErrorCode :: AlterTableError ( format ! (
309+ "table {} has change tracking enabled, modifying columns should be avoided" ,
310+ table_info. desc
311+ ) ) ) ;
312+ }
313+
298314 // construct sql for selecting data from old table.
299315 // computed columns are ignored, as it is build from other columns.
300316 let query_fields = new_schema_without_computed_fields
301317 . fields ( )
302318 . iter ( )
303319 . enumerate ( )
304320 . map ( |( index, field) | {
305- if modified_field_indices . contains ( & index) {
321+ if let Some ( default_scalar ) = modified_default_scalars . get ( & index) {
306322 let old_field = schema. field_with_name ( & field. name ) . unwrap ( ) ;
307323 let need_remove_nullable =
308324 old_field. data_type . is_nullable ( ) && !field. data_type . is_nullable ( ) ;
@@ -427,7 +443,13 @@ impl ModifyTableColumnInterpreter {
427443 }
428444 ( _, _) => {
429445 if need_remove_nullable {
430- format ! ( "remove_nullable(`{}`)" , field. name)
446+ // If the column is being changed from NULLABLE to NOT NULL,
447+ // wrap it with `coalesce()` to replace NULL values with the default,
448+ // and `remove_nullable()` to mark the resulting expression as non-nullable.
449+ format ! (
450+ "remove_nullable(coalesce(`{}`, {}))" ,
451+ field. name, default_scalar
452+ )
431453 } else {
432454 format ! ( "`{}`" , field. name)
433455 }
0 commit comments