github.com/matrixorigin/matrixone@v1.2.0/pkg/sql/plan/build_alter_modify_column.go (about) 1 // Copyright 2023 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package plan 16 17 import ( 18 "context" 19 "strings" 20 21 "github.com/matrixorigin/matrixone/pkg/common/moerr" 22 "github.com/matrixorigin/matrixone/pkg/container/types" 23 "github.com/matrixorigin/matrixone/pkg/pb/plan" 24 "github.com/matrixorigin/matrixone/pkg/pb/timestamp" 25 "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" 26 ) 27 28 // ModifyColumn Can change a column definition but not its name. 29 // More convenient than CHANGE to change a column definition without renaming it. 30 // With FIRST or AFTER, can reorder columns. 31 func ModifyColumn(ctx CompilerContext, alterPlan *plan.AlterTable, spec *tree.AlterTableModifyColumnClause, alterCtx *AlterTableContext) error { 32 tableDef := alterPlan.CopyTableDef 33 34 specNewColumn := spec.NewColumn 35 originalColName := specNewColumn.Name.Parts[0] 36 37 // Check whether added column has existed. 38 colName := specNewColumn.Name.Parts[0] 39 col := FindColumn(tableDef.Cols, originalColName) 40 if col == nil || col.Hidden { 41 return moerr.NewBadFieldError(ctx.GetContext(), colName, alterPlan.TableDef.Name) 42 } 43 44 colType, err := getTypeFromAst(ctx.GetContext(), specNewColumn.Type) 45 if err != nil { 46 return err 47 } 48 if err = checkAddColumnType(ctx.GetContext(), &colType, specNewColumn.Name.Parts[0]); err != nil { 49 return err 50 } 51 52 newCol, err := buildChangeColumnAndConstraint(ctx, alterPlan, col, specNewColumn, colType) 53 if err != nil { 54 return err 55 } 56 57 // Check new column foreign key constraints 58 if err = CheckModifyColumnForeignkeyConstraint(ctx, tableDef, col, newCol); err != nil { 59 return err 60 } 61 62 if isColumnWithPartition(col.Name, tableDef.Partition) { 63 return moerr.NewNotSupported(ctx.GetContext(), "unsupport alter partition part column currently") 64 } 65 66 if err = checkChangeTypeCompatible(ctx.GetContext(), &col.Typ, &newCol.Typ); err != nil { 67 return err 68 } 69 70 if err = checkModifyNewColumn(ctx.GetContext(), tableDef, col, newCol, spec.Position); err != nil { 71 return err 72 } 73 74 alterCtx.alterColMap[newCol.Name] = selectExpr{ 75 sexprType: columnName, 76 sexprStr: col.Name, 77 } 78 79 return nil 80 } 81 82 // checkModifyNewColumn Check the position information of the newly formed column and place the new column in the target location 83 func checkModifyNewColumn(ctx context.Context, tableDef *TableDef, oldCol, newCol *ColDef, pos *tree.ColumnPosition) error { 84 if pos != nil && pos.Typ != tree.ColumnPositionNone { 85 // detete old column 86 originIndex := -1 87 for i, col := range tableDef.Cols { 88 if strings.EqualFold(col.Name, oldCol.Name) { 89 originIndex = i 90 break 91 } 92 } 93 tableDef.Cols = append(tableDef.Cols[:originIndex], tableDef.Cols[originIndex+1:]...) 94 95 targetPos, err := findPositionRelativeColumn(ctx, tableDef.Cols, pos) 96 if err != nil { 97 return err 98 } 99 tableDef.Cols = append(tableDef.Cols[:targetPos], append([]*ColDef{newCol}, tableDef.Cols[targetPos:]...)...) 100 } else { 101 for i, col := range tableDef.Cols { 102 if strings.EqualFold(col.Name, oldCol.Name) { 103 tableDef.Cols[i] = newCol 104 break 105 } 106 } 107 } 108 return nil 109 } 110 111 // Check if the modify column is associated with the partition key 112 func isColumnWithPartition(colName string, partitionDef *PartitionByDef) bool { 113 if partitionDef != nil { 114 if partitionDef.PartitionColumns != nil { 115 for _, column := range partitionDef.PartitionColumns.PartitionColumns { 116 if column == colName { 117 return true 118 } 119 } 120 } else { 121 if strings.EqualFold(partitionDef.PartitionExpr.ExprStr, colName) { 122 return true 123 } 124 } 125 } 126 return false 127 } 128 129 // checkChangeTypeCompatible checks whether changes column type to another is compatible and can be changed. 130 func checkChangeTypeCompatible(ctx context.Context, origin *plan.Type, to *plan.Type) error { 131 // Deal with the same type. 132 if origin.Id == to.Id { 133 return nil 134 } else { 135 if (origin.Id == int32(types.T_time) || origin.Id == int32(types.T_timestamp) || origin.Id == int32(types.T_date) || origin.Id == int32(types.T_datetime) || origin.Id == int32(types.T_char) || origin.Id == int32(types.T_varchar) || origin.Id == int32(types.T_json) || origin.Id == int32(types.T_uuid)) && 136 to.Id == int32(types.T_binary) { 137 return moerr.NewNotSupported(ctx, "currently unsupport change from original type %v to %v ", origin.Id, to.Id) 138 } 139 140 if (origin.Id == int32(types.T_binary) || origin.Id == int32(types.T_decimal64) || origin.Id == int32(types.T_decimal128) || origin.Id == int32(types.T_float32) || origin.Id == int32(types.T_float64)) && 141 (to.Id == int32(types.T_time) || to.Id == int32(types.T_timestamp) || to.Id == int32(types.T_date) || to.Id == int32(types.T_datetime)) { 142 return moerr.NewNotSupported(ctx, "currently unsupport change from original type %v to %v ", origin.Id, to.Id) 143 } 144 } 145 return nil 146 } 147 148 // CheckModifyColumnForeignkeyConstraint check for table column foreign key dependencies, including 149 // the foreign keys of the table itself and being dependent on foreign keys of other tables 150 func CheckModifyColumnForeignkeyConstraint(ctx CompilerContext, tbInfo *TableDef, originalCol, newCol *ColDef) error { 151 if newCol.Typ.GetId() == originalCol.Typ.GetId() && 152 newCol.Typ.GetWidth() == originalCol.Typ.GetWidth() && 153 newCol.Typ.GetAutoIncr() == originalCol.Typ.GetAutoIncr() { 154 return nil 155 } 156 157 for _, fkInfo := range tbInfo.Fkeys { 158 for i, colId := range fkInfo.Cols { 159 if colId == originalCol.ColId { 160 // Check if the parent table of the foreign key exists 161 _, referTableDef := ctx.ResolveById(fkInfo.ForeignTbl, Snapshot{TS: ×tamp.Timestamp{}}) 162 if referTableDef == nil { 163 continue 164 } 165 166 referCol := FindColumnByColId(referTableDef.Cols, fkInfo.ForeignCols[i]) 167 if referCol == nil { 168 continue 169 } 170 if newCol.Typ.GetId() != referCol.Typ.GetId() { 171 return moerr.NewErrForeignKeyColumnCannotChange(ctx.GetContext(), originalCol.Name, fkInfo.Name) 172 } 173 174 if newCol.Typ.GetWidth() < referCol.Typ.GetWidth() || 175 newCol.Typ.GetWidth() < originalCol.Typ.GetWidth() { 176 return moerr.NewErrForeignKeyColumnCannotChange(ctx.GetContext(), originalCol.Name, fkInfo.Name) 177 } 178 } 179 } 180 } 181 182 for _, referredTblId := range tbInfo.RefChildTbls { 183 refObjRef, refTableDef := ctx.ResolveById(referredTblId, Snapshot{TS: ×tamp.Timestamp{}}) 184 if refTableDef == nil { 185 return moerr.NewInternalError(ctx.GetContext(), "The reference foreign key table %d does not exist", referredTblId) 186 } 187 var referredFK *ForeignKeyDef 188 for _, fkInfo := range refTableDef.Fkeys { 189 if fkInfo.ForeignTbl == tbInfo.TblId { 190 referredFK = fkInfo 191 break 192 } 193 } 194 195 for i := range referredFK.Cols { 196 if referredFK.ForeignCols[i] == originalCol.ColId { 197 if originalCol.Name != newCol.Name { 198 return moerr.NewErrAlterOperationNotSupportedReasonFkRename(ctx.GetContext()) 199 } else { 200 return moerr.NewErrForeignKeyColumnCannotChangeChild(ctx.GetContext(), originalCol.Name, referredFK.Name, refObjRef.SchemaName+"."+refTableDef.Name) 201 } 202 203 //childCol := FindColumnByColId(refTableDef.Cols, colId) 204 //if childCol == nil { 205 // continue 206 //} 207 // 208 //if newCol.Typ.GetId() != childCol.Typ.GetId() { 209 // return moerr.NewErrFKIncompatibleColumns(ctx.GetContext(), childCol.Name, originalCol.Name, referredFK.Name) 210 //} 211 // 212 //if newCol.Typ.GetWidth() < childCol.Typ.GetWidth() || 213 // newCol.Typ.GetWidth() < originalCol.Typ.GetWidth() { 214 // return moerr.NewErrForeignKeyColumnCannotChangeChild(ctx.GetContext(), originalCol.Name, referredFK.Name, refObjRef.SchemaName+"."+refTableDef.Name) 215 //} 216 } 217 } 218 } 219 return nil 220 } 221 222 // checkPriKeyConstraint check all parts of a PRIMARY KEY must be NOT NULL 223 func checkPriKeyConstraint(ctx context.Context, col *ColDef, hasDefaultValue, hasNullFlag bool, priKeyDef *plan.PrimaryKeyDef) error { 224 if hasDefaultValue { 225 hasNullFlag = DefaultValueIsNull(col.Default) || hasNullFlag 226 } 227 // Primary key should not be null. 228 if col.Primary && hasDefaultValue && DefaultValueIsNull(col.Default) { 229 return moerr.NewErrInvalidDefault(ctx, col.Name) 230 } 231 // Set primary key flag for outer primary key constraint. 232 // Such as: create table t1 (id int ,name varchar(20), age int, primary key(id, name)) 233 if !col.Primary && priKeyDef != nil { 234 for _, key := range priKeyDef.Names { 235 if key == col.Name { 236 // Primary key should not be null. 237 if hasNullFlag { 238 return moerr.NewErrPrimaryCantHaveNull(ctx) 239 } 240 break 241 } else { 242 continue 243 } 244 } 245 } 246 return nil 247 } 248 249 func DefaultValueIsNull(Default *plan.Default) bool { 250 if Default != nil { 251 if constExpr, ok := Default.GetExpr().Expr.(*plan.Expr_Lit); ok { 252 return constExpr.Lit.Isnull 253 } 254 return false 255 } 256 return false 257 }