vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/ddl.go (about) 1 package planbuilder 2 3 import ( 4 "fmt" 5 6 "vitess.io/vitess/go/vt/key" 7 "vitess.io/vitess/go/vt/sqlparser" 8 "vitess.io/vitess/go/vt/vterrors" 9 "vitess.io/vitess/go/vt/vtgate/engine" 10 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 11 "vitess.io/vitess/go/vt/vtgate/vindexes" 12 ) 13 14 // Error messages for CreateView queries 15 const ( 16 ViewDifferentKeyspace string = "Select query does not belong to the same keyspace as the view statement" 17 ViewComplex string = "Complex select queries are not supported in create or alter view statements" 18 DifferentDestinations string = "Tables or Views specified in the query do not belong to the same destination" 19 ) 20 21 type fkStrategy int 22 23 const ( 24 fkAllow fkStrategy = iota 25 fkDisallow 26 ) 27 28 var fkStrategyMap = map[string]fkStrategy{ 29 "allow": fkAllow, 30 "disallow": fkDisallow, 31 } 32 33 type fkContraint struct { 34 found bool 35 } 36 37 func (fk *fkContraint) FkWalk(node sqlparser.SQLNode) (kontinue bool, err error) { 38 switch node.(type) { 39 case *sqlparser.CreateTable, *sqlparser.AlterTable, 40 *sqlparser.TableSpec, *sqlparser.AddConstraintDefinition, *sqlparser.ConstraintDefinition: 41 return true, nil 42 case *sqlparser.ForeignKeyDefinition: 43 fk.found = true 44 } 45 return false, nil 46 } 47 48 // buildGeneralDDLPlan builds a general DDL plan, which can be either normal DDL or online DDL. 49 // The two behave completely differently, and have two very different primitives. 50 // We want to be able to dynamically choose between normal/online plans according to Session settings. 51 // However, due to caching of plans, we're unable to make that choice right now. In this function we don't have 52 // a session context. It's only when we Execute() the primitive that we have that context. 53 // This is why we return a compound primitive (DDL) which contains fully populated primitives (Send & OnlineDDL), 54 // and which chooses which of the two to invoke at runtime. 55 func buildGeneralDDLPlan(sql string, ddlStatement sqlparser.DDLStatement, reservedVars *sqlparser.ReservedVars, vschema plancontext.VSchema, enableOnlineDDL, enableDirectDDL bool) (*planResult, error) { 56 if vschema.Destination() != nil { 57 return buildByPassDDLPlan(sql, vschema) 58 } 59 normalDDLPlan, onlineDDLPlan, err := buildDDLPlans(sql, ddlStatement, reservedVars, vschema, enableOnlineDDL, enableDirectDDL) 60 if err != nil { 61 return nil, err 62 } 63 64 if ddlStatement.IsTemporary() { 65 err := vschema.ErrorIfShardedF(normalDDLPlan.Keyspace, "temporary table", "Temporary table not supported in sharded database %s", normalDDLPlan.Keyspace.Name) 66 if err != nil { 67 return nil, err 68 } 69 onlineDDLPlan = nil // emptying this so it does not accidentally gets used somewhere 70 } 71 72 eddl := &engine.DDL{ 73 Keyspace: normalDDLPlan.Keyspace, 74 SQL: normalDDLPlan.Query, 75 DDL: ddlStatement, 76 NormalDDL: normalDDLPlan, 77 OnlineDDL: onlineDDLPlan, 78 79 DirectDDLEnabled: enableDirectDDL, 80 OnlineDDLEnabled: enableOnlineDDL, 81 82 CreateTempTable: ddlStatement.IsTemporary(), 83 } 84 tc := &tableCollector{} 85 for _, tbl := range ddlStatement.AffectedTables() { 86 tc.addASTTable(normalDDLPlan.Keyspace.Name, tbl) 87 } 88 89 return newPlanResult(eddl, tc.getTables()...), nil 90 } 91 92 func buildByPassDDLPlan(sql string, vschema plancontext.VSchema) (*planResult, error) { 93 keyspace, err := vschema.DefaultKeyspace() 94 if err != nil { 95 return nil, err 96 } 97 send := &engine.Send{ 98 Keyspace: keyspace, 99 TargetDestination: vschema.Destination(), 100 Query: sql, 101 } 102 return newPlanResult(send), nil 103 } 104 105 func buildDDLPlans(sql string, ddlStatement sqlparser.DDLStatement, reservedVars *sqlparser.ReservedVars, vschema plancontext.VSchema, enableOnlineDDL, enableDirectDDL bool) (*engine.Send, *engine.OnlineDDL, error) { 106 var destination key.Destination 107 var keyspace *vindexes.Keyspace 108 var err error 109 110 switch ddl := ddlStatement.(type) { 111 case *sqlparser.AlterTable, *sqlparser.CreateTable, *sqlparser.TruncateTable: 112 err = checkFKError(vschema, ddlStatement) 113 if err != nil { 114 return nil, nil, err 115 } 116 // For ALTER TABLE and TRUNCATE TABLE, the table must already exist 117 // 118 // For CREATE TABLE, the table may (in the case of --declarative) 119 // already exist. 120 // 121 // We should find the target of the query from this tables location. 122 destination, keyspace, err = findTableDestinationAndKeyspace(vschema, ddlStatement) 123 case *sqlparser.CreateView: 124 destination, keyspace, err = buildCreateView(vschema, ddl, reservedVars, enableOnlineDDL, enableDirectDDL) 125 case *sqlparser.AlterView: 126 destination, keyspace, err = buildAlterView(vschema, ddl, reservedVars, enableOnlineDDL, enableDirectDDL) 127 case *sqlparser.DropView: 128 destination, keyspace, err = buildDropView(vschema, ddlStatement) 129 case *sqlparser.DropTable: 130 destination, keyspace, err = buildDropTable(vschema, ddlStatement) 131 case *sqlparser.RenameTable: 132 destination, keyspace, err = buildRenameTable(vschema, ddl) 133 default: 134 return nil, nil, vterrors.VT13001(fmt.Sprintf("unexpected DDL statement type: %T", ddlStatement)) 135 } 136 137 if err != nil { 138 return nil, nil, err 139 } 140 141 if destination == nil { 142 destination = key.DestinationAllShards{} 143 } 144 145 query := sql 146 // If the query is fully parsed, generate the query from the ast. Otherwise, use the original query 147 if ddlStatement.IsFullyParsed() { 148 query = sqlparser.String(ddlStatement) 149 } 150 151 return &engine.Send{ 152 Keyspace: keyspace, 153 TargetDestination: destination, 154 Query: query, 155 }, &engine.OnlineDDL{ 156 Keyspace: keyspace, 157 TargetDestination: destination, 158 DDL: ddlStatement, 159 SQL: query, 160 }, nil 161 } 162 163 func checkFKError(vschema plancontext.VSchema, ddlStatement sqlparser.DDLStatement) error { 164 if fkStrategyMap[vschema.ForeignKeyMode()] == fkDisallow { 165 fk := &fkContraint{} 166 _ = sqlparser.Walk(fk.FkWalk, ddlStatement) 167 if fk.found { 168 return vterrors.VT10001() 169 } 170 } 171 return nil 172 } 173 174 func findTableDestinationAndKeyspace(vschema plancontext.VSchema, ddlStatement sqlparser.DDLStatement) (key.Destination, *vindexes.Keyspace, error) { 175 var table *vindexes.Table 176 var destination key.Destination 177 var keyspace *vindexes.Keyspace 178 var err error 179 table, _, _, _, destination, err = vschema.FindTableOrVindex(ddlStatement.GetTable()) 180 if err != nil { 181 _, isNotFound := err.(vindexes.NotFoundError) 182 if !isNotFound { 183 return nil, nil, err 184 } 185 } 186 if table == nil { 187 destination, keyspace, _, err = vschema.TargetDestination(ddlStatement.GetTable().Qualifier.String()) 188 if err != nil { 189 return nil, nil, err 190 } 191 ddlStatement.SetTable("", ddlStatement.GetTable().Name.String()) 192 } else { 193 keyspace = table.Keyspace 194 ddlStatement.SetTable("", table.Name.String()) 195 } 196 return destination, keyspace, nil 197 } 198 199 func buildAlterView(vschema plancontext.VSchema, ddl *sqlparser.AlterView, reservedVars *sqlparser.ReservedVars, enableOnlineDDL, enableDirectDDL bool) (key.Destination, *vindexes.Keyspace, error) { 200 // For Alter View, we require that the view exist and the select query can be satisfied within the keyspace itself 201 // We should remove the keyspace name from the table name, as the database name in MySQL might be different than the keyspace name 202 destination, keyspace, err := findTableDestinationAndKeyspace(vschema, ddl) 203 if err != nil { 204 return nil, nil, err 205 } 206 207 selectPlan, err := createInstructionFor(sqlparser.String(ddl.Select), ddl.Select, reservedVars, vschema, enableOnlineDDL, enableDirectDDL) 208 if err != nil { 209 return nil, nil, err 210 } 211 selPlanKs := selectPlan.primitive.GetKeyspaceName() 212 if keyspace.Name != selPlanKs { 213 return nil, nil, vterrors.VT12001(ViewDifferentKeyspace) 214 } 215 if vschema.IsViewsEnabled() { 216 if keyspace == nil { 217 return nil, nil, vterrors.VT09005() 218 } 219 return destination, keyspace, nil 220 } 221 isRoutePlan, opCode := tryToGetRoutePlan(selectPlan.primitive) 222 if !isRoutePlan { 223 return nil, nil, vterrors.VT12001(ViewComplex) 224 } 225 if opCode != engine.Unsharded && opCode != engine.EqualUnique && opCode != engine.Scatter { 226 return nil, nil, vterrors.VT12001(ViewComplex) 227 } 228 _ = sqlparser.SafeRewrite(ddl.Select, nil, func(cursor *sqlparser.Cursor) bool { 229 switch tableName := cursor.Node().(type) { 230 case sqlparser.TableName: 231 cursor.Replace(sqlparser.TableName{ 232 Name: tableName.Name, 233 }) 234 } 235 return true 236 }) 237 return destination, keyspace, nil 238 } 239 240 func buildCreateView(vschema plancontext.VSchema, ddl *sqlparser.CreateView, reservedVars *sqlparser.ReservedVars, enableOnlineDDL, enableDirectDDL bool) (key.Destination, *vindexes.Keyspace, error) { 241 // For Create View, we require that the keyspace exist and the select query can be satisfied within the keyspace itself 242 // We should remove the keyspace name from the table name, as the database name in MySQL might be different than the keyspace name 243 destination, keyspace, _, err := vschema.TargetDestination(ddl.ViewName.Qualifier.String()) 244 if err != nil { 245 return nil, nil, err 246 } 247 ddl.ViewName.Qualifier = sqlparser.NewIdentifierCS("") 248 249 selectPlan, err := createInstructionFor(sqlparser.String(ddl.Select), ddl.Select, reservedVars, vschema, enableOnlineDDL, enableDirectDDL) 250 if err != nil { 251 return nil, nil, err 252 } 253 selPlanKs := selectPlan.primitive.GetKeyspaceName() 254 if keyspace.Name != selPlanKs { 255 return nil, nil, vterrors.VT12001(ViewDifferentKeyspace) 256 } 257 if vschema.IsViewsEnabled() { 258 if keyspace == nil { 259 return nil, nil, vterrors.VT09005() 260 } 261 return destination, keyspace, nil 262 } 263 isRoutePlan, opCode := tryToGetRoutePlan(selectPlan.primitive) 264 if !isRoutePlan { 265 return nil, nil, vterrors.VT12001(ViewComplex) 266 } 267 if opCode != engine.Unsharded && opCode != engine.EqualUnique && opCode != engine.Scatter { 268 return nil, nil, vterrors.VT12001(ViewComplex) 269 } 270 _ = sqlparser.SafeRewrite(ddl.Select, nil, func(cursor *sqlparser.Cursor) bool { 271 switch tableName := cursor.Node().(type) { 272 case sqlparser.TableName: 273 cursor.Replace(sqlparser.TableName{ 274 Name: tableName.Name, 275 }) 276 } 277 return true 278 }) 279 return destination, keyspace, nil 280 } 281 282 func buildDropView(vschema plancontext.VSchema, ddlStatement sqlparser.DDLStatement) (key.Destination, *vindexes.Keyspace, error) { 283 if !vschema.IsViewsEnabled() { 284 return buildDropTable(vschema, ddlStatement) 285 } 286 var ks *vindexes.Keyspace 287 viewMap := make(map[string]any) 288 for _, tbl := range ddlStatement.GetFromTables() { 289 _, ksForView, _, err := vschema.TargetDestination(tbl.Qualifier.String()) 290 if err != nil { 291 return nil, nil, err 292 } 293 if ksForView == nil { 294 return nil, nil, vterrors.VT09005() 295 } 296 if ks == nil { 297 ks = ksForView 298 } else if ks.Name != ksForView.Name { 299 return nil, nil, vterrors.VT12001("cannot drop views from multiple keyspace in a single statement") 300 } 301 if _, exists := viewMap[tbl.Name.String()]; exists { 302 return nil, nil, vterrors.VT03013(tbl.Name.String()) 303 } 304 viewMap[tbl.Name.String()] = nil 305 tbl.Qualifier = sqlparser.NewIdentifierCS("") 306 } 307 return key.DestinationAllShards{}, ks, nil 308 } 309 310 func buildDropTable(vschema plancontext.VSchema, ddlStatement sqlparser.DDLStatement) (key.Destination, *vindexes.Keyspace, error) { 311 var destination key.Destination 312 var keyspace *vindexes.Keyspace 313 for i, tab := range ddlStatement.GetFromTables() { 314 var destinationTab key.Destination 315 var keyspaceTab *vindexes.Keyspace 316 var table *vindexes.Table 317 var err error 318 table, _, _, _, destinationTab, err = vschema.FindTableOrVindex(tab) 319 320 if err != nil { 321 _, isNotFound := err.(vindexes.NotFoundError) 322 if !isNotFound { 323 return nil, nil, err 324 } 325 } 326 if table == nil { 327 destinationTab, keyspaceTab, _, err = vschema.TargetDestination(tab.Qualifier.String()) 328 if err != nil { 329 return nil, nil, err 330 } 331 ddlStatement.GetFromTables()[i] = sqlparser.TableName{ 332 Name: tab.Name, 333 } 334 } else { 335 keyspaceTab = table.Keyspace 336 ddlStatement.GetFromTables()[i] = sqlparser.TableName{ 337 Name: table.Name, 338 } 339 } 340 341 if destination == nil && keyspace == nil { 342 destination = destinationTab 343 keyspace = keyspaceTab 344 } 345 if destination != destinationTab || keyspace != keyspaceTab { 346 return nil, nil, vterrors.VT12001(DifferentDestinations) 347 } 348 } 349 return destination, keyspace, nil 350 } 351 352 func buildRenameTable(vschema plancontext.VSchema, renameTable *sqlparser.RenameTable) (key.Destination, *vindexes.Keyspace, error) { 353 var destination key.Destination 354 var keyspace *vindexes.Keyspace 355 356 for _, tabPair := range renameTable.TablePairs { 357 var destinationFrom key.Destination 358 var keyspaceFrom *vindexes.Keyspace 359 var table *vindexes.Table 360 var err error 361 table, _, _, _, destinationFrom, err = vschema.FindTableOrVindex(tabPair.FromTable) 362 363 if err != nil { 364 _, isNotFound := err.(vindexes.NotFoundError) 365 if !isNotFound { 366 return nil, nil, err 367 } 368 } 369 if table == nil { 370 destinationFrom, keyspaceFrom, _, err = vschema.TargetDestination(tabPair.FromTable.Qualifier.String()) 371 if err != nil { 372 return nil, nil, err 373 } 374 tabPair.FromTable = sqlparser.TableName{ 375 Name: tabPair.FromTable.Name, 376 } 377 } else { 378 keyspaceFrom = table.Keyspace 379 tabPair.FromTable = sqlparser.TableName{ 380 Name: table.Name, 381 } 382 } 383 384 if tabPair.ToTable.Qualifier.String() != "" { 385 _, keyspaceTo, _, err := vschema.TargetDestination(tabPair.ToTable.Qualifier.String()) 386 if err != nil { 387 return nil, nil, err 388 } 389 if keyspaceTo.Name != keyspaceFrom.Name { 390 return nil, nil, vterrors.VT03002(keyspaceFrom.Name, keyspaceTo.Name) 391 } 392 tabPair.ToTable = sqlparser.TableName{ 393 Name: tabPair.ToTable.Name, 394 } 395 } 396 397 if destination == nil && keyspace == nil { 398 destination = destinationFrom 399 keyspace = keyspaceFrom 400 } 401 if destination != destinationFrom || keyspace != keyspaceFrom { 402 return nil, nil, vterrors.VT12001(DifferentDestinations) 403 } 404 } 405 return destination, keyspace, nil 406 } 407 408 func tryToGetRoutePlan(selectPlan engine.Primitive) (valid bool, opCode engine.Opcode) { 409 switch plan := selectPlan.(type) { 410 case *engine.Route: 411 return true, plan.Opcode 412 case engine.Gen4Comparer: 413 return tryToGetRoutePlan(plan.GetGen4Primitive()) 414 default: 415 return false, engine.Opcode(0) 416 } 417 }