vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/dbddl.go (about)

     1  /*
     2  Copyright 2021 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package engine
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	"vitess.io/vitess/go/vt/proto/vtrpc"
    26  	"vitess.io/vitess/go/vt/vterrors"
    27  
    28  	"vitess.io/vitess/go/vt/key"
    29  	"vitess.io/vitess/go/vt/srvtopo"
    30  
    31  	"vitess.io/vitess/go/vt/log"
    32  
    33  	"vitess.io/vitess/go/sqltypes"
    34  	querypb "vitess.io/vitess/go/vt/proto/query"
    35  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    36  )
    37  
    38  var _ Primitive = (*DBDDL)(nil)
    39  
    40  //goland:noinspection GoVarAndConstTypeMayBeOmitted
    41  var databaseCreatorPlugins = map[string]DBDDLPlugin{}
    42  
    43  // DBDDLRegister registers a dbDDL plugin under the specified name.
    44  // A duplicate plugin will generate a panic.
    45  func DBDDLRegister(name string, plugin DBDDLPlugin) {
    46  	if _, ok := databaseCreatorPlugins[name]; ok {
    47  		panic(fmt.Sprintf("%s is already registered", name))
    48  	}
    49  	databaseCreatorPlugins[name] = plugin
    50  }
    51  
    52  // DBDDLPlugin is the interface that you need to implement to add a custom CREATE/DROP DATABASE handler
    53  type DBDDLPlugin interface {
    54  	CreateDatabase(ctx context.Context, name string) error
    55  	DropDatabase(ctx context.Context, name string) error
    56  }
    57  
    58  // DBDDL is just a container around custom database provisioning plugins
    59  // The default behaviour is to just return an error
    60  type DBDDL struct {
    61  	name         string
    62  	create       bool
    63  	queryTimeout int
    64  
    65  	noInputs
    66  	noTxNeeded
    67  }
    68  
    69  // NewDBDDL creates the engine primitive
    70  // `create` will be true for CREATE, and false for DROP
    71  func NewDBDDL(dbName string, create bool, timeout int) *DBDDL {
    72  	return &DBDDL{
    73  		name:         dbName,
    74  		create:       create,
    75  		queryTimeout: timeout,
    76  	}
    77  }
    78  
    79  // RouteType implements the Primitive interface
    80  func (c *DBDDL) RouteType() string {
    81  	if c.create {
    82  		return "CreateDB"
    83  	}
    84  	return "DropDB"
    85  }
    86  
    87  // GetKeyspaceName implements the Primitive interface
    88  func (c *DBDDL) GetKeyspaceName() string {
    89  	return c.name
    90  }
    91  
    92  // GetTableName implements the Primitive interface
    93  func (c *DBDDL) GetTableName() string {
    94  	return ""
    95  }
    96  
    97  // TryExecute implements the Primitive interface
    98  func (c *DBDDL) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
    99  	name := vcursor.GetDBDDLPluginName()
   100  	plugin, ok := databaseCreatorPlugins[name]
   101  	if !ok {
   102  		log.Errorf("'%s' database ddl plugin is not registered. Falling back to default plugin", name)
   103  		plugin = databaseCreatorPlugins[defaultDBDDLPlugin]
   104  	}
   105  	ctx, cancelFunc := addQueryTimeout(ctx, vcursor, c.queryTimeout)
   106  	defer cancelFunc()
   107  
   108  	if c.create {
   109  		return c.createDatabase(ctx, vcursor, plugin)
   110  	}
   111  
   112  	return c.dropDatabase(ctx, vcursor, plugin)
   113  }
   114  
   115  func (c *DBDDL) createDatabase(ctx context.Context, vcursor VCursor, plugin DBDDLPlugin) (*sqltypes.Result, error) {
   116  	err := plugin.CreateDatabase(ctx, c.name)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	var destinations []*srvtopo.ResolvedShard
   121  	for {
   122  		// loop until we have found a valid shard
   123  		destinations, _, err = vcursor.ResolveDestinations(ctx, c.name, nil, []key.Destination{key.DestinationAllShards{}})
   124  		if err == nil {
   125  			break
   126  		}
   127  		select {
   128  		case <-ctx.Done(): //context cancelled
   129  			return nil, vterrors.Errorf(vtrpc.Code_DEADLINE_EXCEEDED, "could not validate create database: destination not resolved")
   130  		case <-time.After(500 * time.Millisecond): //timeout
   131  		}
   132  	}
   133  	var queries []*querypb.BoundQuery
   134  	for range destinations {
   135  		queries = append(queries, &querypb.BoundQuery{
   136  			Sql:           "select 42 from dual where null",
   137  			BindVariables: nil,
   138  		})
   139  	}
   140  
   141  	for {
   142  		_, errors := vcursor.ExecuteMultiShard(ctx, c, destinations, queries, false, true)
   143  
   144  		noErr := true
   145  		for _, err := range errors {
   146  			if err != nil {
   147  				noErr = false
   148  				select {
   149  				case <-ctx.Done(): //context cancelled
   150  					return nil, vterrors.Errorf(vtrpc.Code_DEADLINE_EXCEEDED, "could not validate create database: tablets not healthy")
   151  				case <-time.After(500 * time.Millisecond): //timeout
   152  				}
   153  				break
   154  			}
   155  		}
   156  		if noErr {
   157  			break
   158  		}
   159  	}
   160  	return &sqltypes.Result{RowsAffected: 1}, nil
   161  }
   162  
   163  func (c *DBDDL) dropDatabase(ctx context.Context, vcursor VCursor, plugin DBDDLPlugin) (*sqltypes.Result, error) {
   164  	err := plugin.DropDatabase(ctx, c.name)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	for vcursor.KeyspaceAvailable(c.name) {
   169  		select {
   170  		case <-ctx.Done(): //context cancelled
   171  			return nil, vterrors.Errorf(vtrpc.Code_DEADLINE_EXCEEDED, "could not validate drop database: keyspace still available in vschema")
   172  		case <-time.After(500 * time.Millisecond): //timeout
   173  		}
   174  	}
   175  
   176  	return &sqltypes.Result{StatusFlags: sqltypes.ServerStatusDbDropped}, nil
   177  }
   178  
   179  // TryStreamExecute implements the Primitive interface
   180  func (c *DBDDL) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
   181  	res, err := c.TryExecute(ctx, vcursor, bindVars, wantfields)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	return callback(res)
   186  }
   187  
   188  // GetFields implements the Primitive interface
   189  func (c *DBDDL) GetFields(context.Context, VCursor, map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
   190  	return &sqltypes.Result{}, nil
   191  }
   192  
   193  // description implements the Primitive interface
   194  func (c *DBDDL) description() PrimitiveDescription {
   195  	return PrimitiveDescription{
   196  		OperatorType: strings.ToUpper(c.RouteType()),
   197  		Keyspace:     &vindexes.Keyspace{Name: c.name},
   198  	}
   199  }