github.com/matrixorigin/matrixone@v1.2.0/pkg/sql/plan/partition_list.go (about) 1 // Copyright 2022 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 20 "github.com/matrixorigin/matrixone/pkg/catalog" 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/sql/parsers/dialect" 25 "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect/mysql" 26 "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" 27 "github.com/matrixorigin/matrixone/pkg/sql/util" 28 ) 29 30 type listPartitionBuilder struct { 31 addPartitions []*plan.PartitionItem 32 addPartitionSubTableDefs []*TableDef 33 newPartitionDef *PartitionByDef 34 } 35 36 // buildListPartitiion handle List Partitioning and List columns partitioning 37 func (lpb *listPartitionBuilder) build(ctx context.Context, partitionBinder *PartitionBinder, stmt *tree.CreateTable, tableDef *TableDef) error { 38 partitionSyntaxDef := stmt.PartitionOption 39 partitionCount, err := getValidPartitionCount(ctx, true, partitionSyntaxDef) 40 if err != nil { 41 return err 42 } 43 partitionType := partitionSyntaxDef.PartBy.PType.(*tree.ListType) 44 45 partitionDef := &plan.PartitionByDef{ 46 IsSubPartition: partitionSyntaxDef.PartBy.IsSubPartition, 47 Partitions: make([]*plan.PartitionItem, partitionCount), 48 PartitionNum: partitionCount, 49 } 50 51 if len(partitionType.ColumnList) == 0 { 52 //PARTITION BY LIST(expr) 53 partitionDef.Type = plan.PartitionType_LIST 54 err := buildPartitionExpr(ctx, tableDef, partitionBinder, partitionDef, partitionType.Expr) 55 if err != nil { 56 return err 57 } 58 } else { 59 //PARTITION BY LIST COLUMNS(col1,col2,...) 60 partitionDef.Type = plan.PartitionType_LIST_COLUMNS 61 err = buildPartitionColumns(ctx, tableDef, partitionBinder, partitionDef, partitionType.ColumnList) 62 if err != nil { 63 return err 64 } 65 } 66 67 err = lpb.buildPartitionDefs(ctx, partitionBinder, partitionDef, partitionSyntaxDef.Partitions) 68 if err != nil { 69 return err 70 } 71 72 err = lpb.checkPartitionIntegrity(ctx, partitionBinder, tableDef, partitionDef) 73 if err != nil { 74 return err 75 } 76 77 err = lpb.buildEvalPartitionExpression(ctx, partitionBinder, stmt.PartitionOption, partitionDef) 78 if err != nil { 79 return err 80 } 81 82 //partitionDef.PartitionMsg = tree.String(partitionSyntaxDef, dialect.MYSQL) 83 partitionDef.PartitionMsg = tree.StringWithOpts(partitionSyntaxDef, dialect.MYSQL, tree.WithSingleQuoteString()) 84 tableDef.Partition = partitionDef 85 return nil 86 } 87 88 func (lpb *listPartitionBuilder) buildPartitionDefs(ctx context.Context, partitionBinder *PartitionBinder, partitionDef *plan.PartitionByDef, defs []*tree.Partition) (err error) { 89 for i, partition := range defs { 90 name := string(partition.Name) 91 partitionItem := &plan.PartitionItem{ 92 PartitionName: name, 93 OrdinalPosition: uint32(i + 1), 94 } 95 96 if valuesIn, ok := partition.Values.(*tree.ValuesIn); ok { 97 valueList := valuesIn.ValueList 98 if len(valueList) == 0 { 99 return moerr.NewInvalidInput(ctx, "there is no value in value list") 100 } 101 binder := NewPartitionBinder(nil, nil) 102 inValues := make([]*plan.Expr, len(valueList)) 103 104 for j, value := range valueList { 105 tuple, err := binder.BindExpr(value, 0, false) 106 if err != nil { 107 return err 108 } 109 inValues[j] = tuple 110 } 111 partitionItem.InValues = inValues 112 partitionItem.Description = tree.String(valuesIn, dialect.MYSQL) 113 } else { 114 return moerr.NewInternalError(ctx, "LIST PARTITIONING can only use VALUES IN definition") 115 } 116 for _, tableOption := range partition.Options { 117 if opComment, ok := tableOption.(*tree.TableOptionComment); ok { 118 partitionItem.Comment = opComment.Comment 119 } 120 } 121 partitionDef.Partitions[i] = partitionItem 122 } 123 return buildListPartitionItem(partitionBinder, partitionDef, defs) 124 } 125 126 func (lpb *listPartitionBuilder) checkPartitionIntegrity(ctx context.Context, partitionBinder *PartitionBinder, tableDef *TableDef, partitionDef *plan.PartitionByDef) error { 127 if err := checkPartitionExprType(ctx, partitionBinder, tableDef, partitionDef); err != nil { 128 return err 129 } 130 if err := checkPartitionDefines(ctx, partitionBinder, partitionDef, tableDef); err != nil { 131 return err 132 } 133 if err := checkPartitionKeys(ctx, partitionBinder.builder.nameByColRef, tableDef, partitionDef); err != nil { 134 return err 135 } 136 if partitionDef.Type == plan.PartitionType_KEY || partitionDef.Type == plan.PartitionType_LINEAR_KEY { 137 if len(partitionDef.PartitionColumns.Columns) == 0 { 138 return handleEmptyKeyPartition(partitionBinder, tableDef, partitionDef) 139 } 140 } 141 return nil 142 } 143 144 func (lpb *listPartitionBuilder) buildEvalPartitionExpression(ctx context.Context, partitionBinder *PartitionBinder, stmt *tree.PartitionOption, partitionDef *plan.PartitionByDef) error { 145 partitionType := stmt.PartBy.PType.(*tree.ListType) 146 // For the List partition, convert the partition information into the expression, such as: 147 // case when expr in (1, 5, 9, 13, 17) then 0 when expr in (2, 6, 10, 14, 18) then 1 else -1 end 148 if partitionType.ColumnList == nil { 149 listExpr := partitionType.Expr 150 partitionExprAst, err := buildListCaseWhenExpr(listExpr, stmt.Partitions) 151 if err != nil { 152 return err 153 } 154 tempExpr, err := partitionBinder.baseBindExpr(partitionExprAst, 0, true) 155 if err != nil { 156 return err 157 } 158 partitionExpression, err := appendCastBeforeExpr(ctx, tempExpr, plan.Type{ 159 Id: int32(types.T_int32), 160 NotNullable: true, 161 }) 162 if err != nil { 163 return err 164 } 165 partitionDef.PartitionExpression = partitionExpression 166 } else { 167 // For the List Columns partition, convert the partition information into the expression, such as: 168 // case when col1 = 0 and col2 = 0 or col1 = null and col2 = null then 0 169 // when col1 = 0 and col2 = 1 or col1 = 0 and col2 = 2 or col1 = 0 and col2 = 3 then 1 170 // when col1 = 1 and col2 = 0 or col1 = 2 and col2 = 0 or col1 = 2 and col2 = 1 then 2 171 // else -1 end 172 columnsExpr := partitionType.ColumnList 173 partitionExprAst, err := buildListColumnsCaseWhenExpr(columnsExpr, stmt.Partitions) 174 if err != nil { 175 return err 176 } 177 tempExpr, err := partitionBinder.baseBindExpr(partitionExprAst, 0, true) 178 if err != nil { 179 return err 180 } 181 partitionExpression, err := appendCastBeforeExpr(ctx, tempExpr, plan.Type{ 182 Id: int32(types.T_int32), 183 NotNullable: true, 184 }) 185 if err != nil { 186 return err 187 } 188 partitionDef.PartitionExpression = partitionExpression 189 } 190 return nil 191 } 192 193 // buildAddPartition Perform `ADD PARTITION` semantic check on the list partition table 194 func (lpb *listPartitionBuilder) buildAddPartition(ctx context.Context, partitionBinder *PartitionBinder, stmt *tree.AlterPartitionAddPartitionClause, tableDef *TableDef) error { 195 partitionInfo := DeepCopyPartitionByDef(tableDef.Partition) 196 197 err := lpb.buildAddPartitionDefinitions(ctx, partitionBinder, partitionInfo, stmt.Partitions) 198 if err != nil { 199 return err 200 } 201 202 err = lpb.checkPartitionIntegrity(ctx, partitionBinder, tableDef, partitionInfo) 203 if err != nil { 204 return err 205 } 206 207 //------------------------------------------------------------------------------------------------------------------ 208 // Regenerate the syntax tree for the partition by clause 209 ast, err := mysql.ParseOne(ctx, "create table t1() "+partitionInfo.PartitionMsg, 1, 0) 210 if err != nil { 211 return err 212 } 213 214 createTable, ok := ast.(*tree.CreateTable) 215 if !ok { 216 panic("Partition definition statement Parser restore failed") 217 } 218 219 // Regenerate partition calculation expression 220 partitionBy := createTable.PartitionOption 221 partitionBy.Partitions = append(partitionBy.Partitions, stmt.Partitions...) 222 err = lpb.buildEvalPartitionExpression(ctx, partitionBinder, partitionBy, partitionInfo) 223 if err != nil { 224 return err 225 } 226 227 // Generate a new partition by clause's restore string 228 partitionInfo.PartitionMsg = tree.StringWithOpts(partitionBy, dialect.MYSQL, tree.WithSingleQuoteString()) 229 230 // Construct partition subtable `TableDef` for newly added partitions 231 err = lpb.makePartitionSubTableDef(ctx, tableDef, partitionInfo, lpb.addPartitions) 232 if err != nil { 233 return err 234 } 235 return nil 236 } 237 238 // Perform semantic check for newly added partition definitions 239 func (lpb *listPartitionBuilder) buildAddPartitionDefinitions(ctx context.Context, partitionBinder *PartitionBinder, newPartitionDef *plan.PartitionByDef, defs []*tree.Partition) (err error) { 240 srcLen := len(newPartitionDef.Partitions) 241 addPartitions := make([]*plan.PartitionItem, len(defs)) 242 243 for i, partition := range defs { 244 name := string(partition.Name) 245 partitionItem := &plan.PartitionItem{ 246 PartitionName: name, 247 OrdinalPosition: uint32(srcLen + i + 1), 248 } 249 250 if valuesIn, ok := partition.Values.(*tree.ValuesIn); ok { 251 valueList := valuesIn.ValueList 252 if len(valueList) == 0 { 253 return moerr.NewInvalidInput(ctx, "there is no value in value list") 254 } 255 binder := NewPartitionBinder(nil, nil) 256 inValues := make([]*plan.Expr, len(valueList)) 257 258 for j, value := range valueList { 259 tuple, err := binder.BindExpr(value, 0, false) 260 if err != nil { 261 return err 262 } 263 inValues[j] = tuple 264 } 265 partitionItem.InValues = inValues 266 partitionItem.Description = tree.String(valuesIn, dialect.MYSQL) 267 } else { 268 return moerr.NewInternalError(ctx, "LIST PARTITIONING can only use VALUES IN definition") 269 } 270 for _, tableOption := range partition.Options { 271 if opComment, ok := tableOption.(*tree.TableOptionComment); ok { 272 partitionItem.Comment = opComment.Comment 273 } 274 } 275 addPartitions[i] = partitionItem 276 newPartitionDef.Partitions = append(newPartitionDef.Partitions, partitionItem) 277 } 278 lpb.addPartitions = addPartitions 279 return buildListPartitionItem(partitionBinder, newPartitionDef, defs) 280 } 281 282 // makePartitionSubTableDef Construct List partition subtable `TableDef` for newly added partitions 283 func (lpb *listPartitionBuilder) makePartitionSubTableDef(ctx context.Context, tableDef *TableDef, newPartitionDef *plan.PartitionByDef, alterAddPartition []*plan.PartitionItem) error { 284 //add partition table 285 //there is no index for the partition table 286 //there is no foreign key for the partition table 287 mainTableName := tableDef.Name 288 if !util.IsValidNameForPartitionTable(mainTableName) { 289 return moerr.NewInvalidInput(ctx, "invalid main table name %s", mainTableName) 290 } 291 292 // common properties 293 partitionProps := []*plan.Property{ 294 { 295 Key: catalog.SystemRelAttr_Kind, 296 Value: catalog.SystemPartitionRel, 297 }, 298 { 299 Key: catalog.SystemRelAttr_CreateSQL, 300 Value: "", 301 }, 302 } 303 partitionPropsDef := &plan.TableDef_DefType{ 304 Def: &plan.TableDef_DefType_Properties{ 305 Properties: &plan.PropertiesDef{ 306 Properties: partitionProps, 307 }, 308 }} 309 310 partitionTableDefs := make([]*TableDef, len(alterAddPartition)) 311 partitionTableNames := make([]string, len(alterAddPartition)) 312 313 for i := 0; i < len(alterAddPartition); i++ { 314 part := alterAddPartition[i] 315 ok, partitionTableName := util.MakeNameOfPartitionTable(part.GetPartitionName(), mainTableName) 316 if !ok { 317 return moerr.NewInvalidInput(ctx, "invalid partition table name %s", partitionTableName) 318 } 319 320 // save the table name for a partition 321 part.PartitionTableName = partitionTableName 322 partitionTableNames[i] = partitionTableName 323 324 partitionTableDefs[i] = &TableDef{ 325 Name: partitionTableName, 326 Cols: deepCopyTableCols(tableDef.Cols, true), //same as the main table's column defs 327 } 328 329 partitionTableDefs[i].Pkey = tableDef.GetPkey() 330 partitionTableDefs[i].Defs = append(partitionTableDefs[i].Defs, partitionPropsDef) 331 } 332 newPartitionDef.PartitionTableNames = append(newPartitionDef.PartitionTableNames, partitionTableNames...) 333 newPartitionDef.PartitionNum = newPartitionDef.PartitionNum + uint64(len(alterAddPartition)) 334 lpb.newPartitionDef = newPartitionDef 335 lpb.addPartitionSubTableDefs = partitionTableDefs 336 return nil 337 }