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  }