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 }