2020#include " duckdb/planner/operator/logical_projection.hpp"
2121#include " duckdb/planner/operator/logical_update.hpp"
2222#include " duckdb/planner/parsed_data/bound_create_table_info.hpp"
23+ #include " duckdb/parser/expression/function_expression.hpp"
2324#include " duckdb/storage/storage_manager.hpp"
2425#include " duckdb/storage/table_storage_info.hpp"
2526
@@ -180,6 +181,10 @@ unique_ptr<CatalogEntry> DuckTableEntry::AlterEntry(ClientContext &context, Alte
180181 auto &rename_info = table_info.Cast <RenameColumnInfo>();
181182 return RenameColumn (context, rename_info);
182183 }
184+ case AlterTableType::RENAME_FIELD: {
185+ auto &rename_info = table_info.Cast <RenameFieldInfo>();
186+ return RenameField (context, rename_info);
187+ }
183188 case AlterTableType::RENAME_TABLE: {
184189 auto &rename_info = table_info.Cast <RenameTableInfo>();
185190 auto copied_table = Copy (context);
@@ -191,10 +196,18 @@ unique_ptr<CatalogEntry> DuckTableEntry::AlterEntry(ClientContext &context, Alte
191196 auto &add_info = table_info.Cast <AddColumnInfo>();
192197 return AddColumn (context, add_info);
193198 }
199+ case AlterTableType::ADD_FIELD: {
200+ auto &add_info = table_info.Cast <AddFieldInfo>();
201+ return AddField (context, add_info);
202+ }
194203 case AlterTableType::REMOVE_COLUMN: {
195204 auto &remove_info = table_info.Cast <RemoveColumnInfo>();
196205 return RemoveColumn (context, remove_info);
197206 }
207+ case AlterTableType::REMOVE_FIELD: {
208+ auto &remove_info = table_info.Cast <RemoveFieldInfo>();
209+ return RemoveField (context, remove_info);
210+ }
198211 case AlterTableType::SET_DEFAULT: {
199212 auto &set_default_info = table_info.Cast <SetDefaultInfo>();
200213 return SetDefault (context, set_default_info);
@@ -361,6 +374,119 @@ unique_ptr<CatalogEntry> DuckTableEntry::AddColumn(ClientContext &context, AddCo
361374 return make_uniq<DuckTableEntry>(catalog, schema, *bound_create_info, new_storage);
362375}
363376
377+ struct StructMappingInfo {
378+ LogicalType new_type;
379+ unique_ptr<ParsedExpression> default_value;
380+ ErrorData error;
381+ };
382+
383+ unique_ptr<ParsedExpression> PackExpression (unique_ptr<ParsedExpression> expr, string name) {
384+ expr->SetAlias (std::move (name));
385+ vector<unique_ptr<ParsedExpression>> children;
386+ children.push_back (std::move (expr));
387+ auto res = make_uniq<FunctionExpression>(" struct_pack" , std::move (children));
388+ return std::move (res);
389+ }
390+
391+ Value ConstructMapping (const string &name, const LogicalType &type) {
392+ if (type.id () != LogicalTypeId::STRUCT) {
393+ return Value (name);
394+ }
395+ child_list_t <Value> child_mapping;
396+ auto &child_types = StructType::GetChildTypes (type);
397+ for (auto &entry : child_types) {
398+ auto mapping_value = ConstructMapping (entry.first , entry.second );
399+ if (entry.second .id () == LogicalTypeId::STRUCT) {
400+ child_list_t <Value> child_values;
401+ child_values.emplace_back (string (), Value (entry.first ));
402+ child_values.emplace_back (string (), std::move (mapping_value));
403+ mapping_value = Value::STRUCT (std::move (child_values));
404+ }
405+ child_mapping.emplace_back (entry.first , std::move (mapping_value));
406+ }
407+ return Value::STRUCT (std::move (child_mapping));
408+ }
409+
410+ StructMappingInfo AddFieldToStruct (const LogicalType &type, const vector<string> &column_path,
411+ const ColumnDefinition &new_field, idx_t depth = 0 ) {
412+ if (type.id () != LogicalTypeId::STRUCT) {
413+ throw BinderException (" Column %s is not a struct - ALTER TABLE can only add fields to structs" ,
414+ column_path[depth]);
415+ }
416+ StructMappingInfo result;
417+ auto child_list = StructType::GetChildTypes (type);
418+ if (column_path.size () == depth + 1 ) {
419+ // root path - we are adding at this level
420+ // check if a field with this name already exists
421+ for (auto &entry : child_list) {
422+ if (StringUtil::CIEquals (entry.first , new_field.Name ())) {
423+ // already exists!
424+ result.error = ErrorData (CatalogException (" Duplicate field \" %s\" - field already exists in struct %s" ,
425+ new_field.Name (), column_path.back ()));
426+ return result;
427+ }
428+ }
429+ // add the new type
430+ child_list.emplace_back (new_field.Name (), new_field.Type ());
431+ result.new_type = LogicalType::STRUCT (std::move (child_list));
432+ // set the default value
433+ unique_ptr<ParsedExpression> default_value;
434+ if (new_field.HasDefaultValue ()) {
435+ default_value = new_field.DefaultValue ().Copy ();
436+ } else {
437+ default_value = make_uniq<ConstantExpression>(Value (new_field.Type ()));
438+ }
439+ result.default_value = PackExpression (std::move (default_value), new_field.Name ());
440+ return result;
441+ }
442+ // not the root path - we need to recurse
443+ auto &next_component = column_path[depth + 1 ];
444+ bool found = false ;
445+ for (auto &entry : child_list) {
446+ if (StringUtil::CIEquals (entry.first , next_component)) {
447+ // found the entry - recurse
448+ auto child_res = AddFieldToStruct (entry.second , column_path, new_field, depth + 1 );
449+ if (child_res.error .HasError ()) {
450+ return child_res;
451+ }
452+ entry.second = std::move (child_res.new_type );
453+ result.default_value = PackExpression (std::move (child_res.default_value ), entry.first );
454+ found = true ;
455+ break ;
456+ }
457+ }
458+ result.new_type = LogicalType::STRUCT (std::move (child_list));
459+ if (!found) {
460+ throw BinderException (" Sub-field %s does not exist in column %s" , next_component, column_path[depth]);
461+ }
462+ return result;
463+ }
464+
465+ unique_ptr<CatalogEntry> DuckTableEntry::AddField (ClientContext &context, AddFieldInfo &info) {
466+ // follow the path
467+ auto &col = GetColumn (info.column_path [0 ]);
468+ auto res = AddFieldToStruct (col.Type (), info.column_path , info.new_field );
469+ if (res.error .HasError ()) {
470+ if (!info.if_field_not_exists ) {
471+ res.error .Throw ();
472+ }
473+ return nullptr ;
474+ }
475+
476+ // construct the struct remapping expression
477+ vector<unique_ptr<ParsedExpression>> children;
478+ children.push_back (make_uniq<ColumnRefExpression>(info.column_path [0 ]));
479+ children.push_back (make_uniq<ConstantExpression>(Value (res.new_type )));
480+ children.push_back (make_uniq<ConstantExpression>(ConstructMapping (col.Name (), col.Type ())));
481+ children.push_back (std::move (res.default_value ));
482+
483+ auto function = make_uniq<FunctionExpression>(" remap_struct" , std::move (children));
484+
485+ ChangeColumnTypeInfo change_column_type (info.GetAlterEntryData (), info.column_path [0 ], std::move (res.new_type ),
486+ std::move (function));
487+ return ChangeColumnType (context, change_column_type);
488+ }
489+
364490void DuckTableEntry::UpdateConstraintsOnColumnDrop (const LogicalIndex &removed_index,
365491 const vector<LogicalIndex> &adjusted_indices,
366492 const RemoveColumnInfo &info, CreateTableInfo &create_info,
@@ -501,6 +627,190 @@ unique_ptr<CatalogEntry> DuckTableEntry::RemoveColumn(ClientContext &context, Re
501627 return make_uniq<DuckTableEntry>(catalog, schema, *bound_create_info, new_storage);
502628}
503629
630+ struct DroppedFieldMapping {
631+ Value mapping;
632+ LogicalType new_type;
633+ ErrorData error;
634+ };
635+
636+ DroppedFieldMapping DropFieldFromStruct (const LogicalType &type, const vector<string> &column_path, idx_t depth) {
637+ if (type.id () != LogicalTypeId::STRUCT) {
638+ throw CatalogException (" Cannot drop field from column \" %s\" - not a struct" , column_path[0 ]);
639+ }
640+ auto &dropped_entry = column_path[depth];
641+ bool last_entry = depth + 1 == column_path.size ();
642+ bool found = false ;
643+ DroppedFieldMapping result;
644+ child_list_t <Value> child_mapping;
645+ child_list_t <LogicalType> new_type_children;
646+ auto &child_types = StructType::GetChildTypes (type);
647+ for (auto &entry : child_types) {
648+ Value mapping_value;
649+ LogicalType type_value;
650+ if (StringUtil::CIEquals (entry.first , dropped_entry)) {
651+ // this is the entry we are dropping
652+ found = true ;
653+ if (last_entry) {
654+ // we are dropping this entry in its entirety - just skip
655+ if (child_types.size () == 1 ) {
656+ throw CatalogException (" Cannot drop field %s from column %s - it is the last field of the struct" ,
657+ column_path.back (), column_path.front ());
658+ }
659+ continue ;
660+ } else {
661+ // we are dropping a field in this entry - recurse
662+ auto child_result = DropFieldFromStruct (entry.second , column_path, depth + 1 );
663+ if (child_result.error .HasError ()) {
664+ // bubble up error
665+ return child_result;
666+ }
667+ mapping_value = std::move (child_result.mapping );
668+ type_value = std::move (child_result.new_type );
669+ }
670+ } else {
671+ // we are not adjusting this entry - copy the type and create a straightforward mapping
672+ mapping_value = ConstructMapping (entry.first , entry.second );
673+ type_value = entry.second ;
674+ }
675+ if (entry.second .id () == LogicalTypeId::STRUCT) {
676+ child_list_t <Value> child_values;
677+ child_values.emplace_back (string (), Value (entry.first ));
678+ child_values.emplace_back (string (), std::move (mapping_value));
679+ mapping_value = Value::STRUCT (std::move (child_values));
680+ }
681+ child_mapping.emplace_back (entry.first , std::move (mapping_value));
682+ new_type_children.emplace_back (entry.first , type_value);
683+ }
684+ if (!found) {
685+ result.error = ErrorData (CatalogException (" Cannot drop field \" %s\" - it does not exist" , dropped_entry));
686+ } else {
687+ result.mapping = Value::STRUCT (std::move (child_mapping));
688+ result.new_type = LogicalType::STRUCT (std::move (new_type_children));
689+ }
690+ return result;
691+ }
692+
693+ unique_ptr<CatalogEntry> DuckTableEntry::RemoveField (ClientContext &context, RemoveFieldInfo &info) {
694+ if (!ColumnExists (info.column_path [0 ])) {
695+ if (!info.if_column_exists ) {
696+ throw CatalogException (" Cannot drop field from column \" %s\" - it does not exist" , info.column_path [0 ]);
697+ }
698+ return nullptr ;
699+ }
700+ // follow the path
701+ auto &col = GetColumn (info.column_path [0 ]);
702+ auto res = DropFieldFromStruct (col.Type (), info.column_path , 1 );
703+ if (res.error .HasError ()) {
704+ if (!info.if_column_exists ) {
705+ res.error .Throw ();
706+ }
707+ return nullptr ;
708+ }
709+
710+ // construct the struct remapping expression
711+ vector<unique_ptr<ParsedExpression>> children;
712+ children.push_back (make_uniq<ColumnRefExpression>(info.column_path [0 ]));
713+ children.push_back (make_uniq<ConstantExpression>(Value (res.new_type )));
714+ children.push_back (make_uniq<ConstantExpression>(std::move (res.mapping )));
715+ children.push_back (make_uniq<ConstantExpression>(Value ()));
716+
717+ auto function = make_uniq<FunctionExpression>(" remap_struct" , std::move (children));
718+
719+ ChangeColumnTypeInfo change_column_type (info.GetAlterEntryData (), info.column_path [0 ], std::move (res.new_type ),
720+ std::move (function));
721+ return ChangeColumnType (context, change_column_type);
722+ }
723+
724+ DroppedFieldMapping RenameFieldFromStruct (const LogicalType &type, const vector<string> &column_path,
725+ const string &new_name, idx_t depth) {
726+ if (type.id () != LogicalTypeId::STRUCT) {
727+ throw CatalogException (" Cannot rename field from column \" %s\" - not a struct" , column_path[0 ]);
728+ }
729+ auto &rename_entry = column_path[depth];
730+ bool last_entry = depth + 1 == column_path.size ();
731+ bool found = false ;
732+ DroppedFieldMapping result;
733+ child_list_t <Value> child_mapping;
734+ child_list_t <LogicalType> new_type_children;
735+ auto &child_types = StructType::GetChildTypes (type);
736+ for (auto &entry : child_types) {
737+ auto field_name = entry.first ;
738+ Value mapping_value;
739+ LogicalType type_value;
740+ if (StringUtil::CIEquals (field_name, rename_entry)) {
741+ // this is the entry we are dropping
742+ found = true ;
743+ if (last_entry) {
744+ // we are renaming this entry
745+ for (auto &sub_entry : child_types) {
746+ if (StringUtil::CIEquals (new_name, sub_entry.first )) {
747+ throw CatalogException (
748+ " Cannot rename field %s from column %s to %s - a field with this name already exists" ,
749+ column_path.back (), column_path.front (), new_name);
750+ }
751+ }
752+ field_name = new_name;
753+ mapping_value = ConstructMapping (entry.first , entry.second );
754+ type_value = entry.second ;
755+ } else {
756+ // we are dropping a field in this entry - recurse
757+ auto child_result = RenameFieldFromStruct (entry.second , column_path, new_name, depth + 1 );
758+ if (child_result.error .HasError ()) {
759+ // bubble up error
760+ return child_result;
761+ }
762+ mapping_value = std::move (child_result.mapping );
763+ type_value = std::move (child_result.new_type );
764+ }
765+ } else {
766+ // we are not adjusting this entry - copy the type and create a straightforward mapping
767+ mapping_value = ConstructMapping (entry.first , entry.second );
768+ type_value = entry.second ;
769+ }
770+ if (entry.second .id () == LogicalTypeId::STRUCT) {
771+ child_list_t <Value> child_values;
772+ child_values.emplace_back (string (), Value (entry.first ));
773+ child_values.emplace_back (string (), std::move (mapping_value));
774+ mapping_value = Value::STRUCT (std::move (child_values));
775+ }
776+ child_mapping.emplace_back (field_name, std::move (mapping_value));
777+ new_type_children.emplace_back (field_name, type_value);
778+ }
779+ if (!found) {
780+ result.error = ErrorData (CatalogException (" Cannot rename field \" %s\" - it does not exist" , rename_entry));
781+ } else {
782+ result.mapping = Value::STRUCT (std::move (child_mapping));
783+ result.new_type = LogicalType::STRUCT (std::move (new_type_children));
784+ }
785+ return result;
786+ }
787+
788+ unique_ptr<CatalogEntry> DuckTableEntry::RenameField (ClientContext &context, RenameFieldInfo &info) {
789+ if (!ColumnExists (info.column_path [0 ])) {
790+ throw CatalogException (" Cannot rename field from column \" %s\" - it does not exist" , info.column_path [0 ]);
791+ }
792+ // follow the path
793+ auto &col = GetColumn (info.column_path [0 ]);
794+ auto res = RenameFieldFromStruct (col.Type (), info.column_path , info.new_name , 1 );
795+ if (res.error .HasError ()) {
796+ res.error .Throw ();
797+ return nullptr ;
798+ }
799+
800+ // construct the struct remapping expression
801+ vector<unique_ptr<ParsedExpression>> children;
802+ children.push_back (make_uniq<ColumnRefExpression>(info.column_path [0 ]));
803+ children.push_back (make_uniq<ConstantExpression>(Value (res.new_type )));
804+ children.push_back (make_uniq<ConstantExpression>(std::move (res.mapping )));
805+ children.push_back (make_uniq<ConstantExpression>(Value ()));
806+
807+ auto function = make_uniq<FunctionExpression>(" remap_struct" , std::move (children));
808+
809+ ChangeColumnTypeInfo change_column_type (info.GetAlterEntryData (), info.column_path [0 ], std::move (res.new_type ),
810+ std::move (function));
811+ return ChangeColumnType (context, change_column_type);
812+ }
813+
504814unique_ptr<CatalogEntry> DuckTableEntry::SetDefault (ClientContext &context, SetDefaultInfo &info) {
505815 auto create_info = make_uniq<CreateTableInfo>(schema, name);
506816 create_info->comment = comment;
0 commit comments