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: &timestamp.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: &timestamp.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  }