vitess.io/vitess@v0.16.2/go/cmd/vtctldclient/command/schema.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 command
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/spf13/cobra"
    27  
    28  	"vitess.io/vitess/go/cmd/vtctldclient/cli"
    29  	"vitess.io/vitess/go/protoutil"
    30  	"vitess.io/vitess/go/vt/logutil"
    31  	"vitess.io/vitess/go/vt/schema"
    32  	"vitess.io/vitess/go/vt/sqlparser"
    33  	"vitess.io/vitess/go/vt/topo/topoproto"
    34  	"vitess.io/vitess/go/vt/wrangler"
    35  
    36  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    37  	"vitess.io/vitess/go/vt/proto/vtrpc"
    38  )
    39  
    40  var (
    41  	// ApplySchema makes an ApplySchema gRPC call to a vtctld.
    42  	ApplySchema = &cobra.Command{
    43  		Use:   "ApplySchema [--allow-long-unavailability] [--ddl-strategy <strategy>] [--uuid <uuid> ...] [--migration-context <context>] [--wait-replicas-timeout <duration>] [--skip-preflight] [--caller-id <caller_id>] {--sql-file <file> | --sql <sql>} <keyspace>",
    44  		Short: "Applies the schema change to the specified keyspace on every primary, running in parallel on all shards. The changes are then propagated to replicas via replication.",
    45  		Long: `Applies the schema change to the specified keyspace on every primary, running in parallel on all shards. The changes are then propagated to replicas via replication.
    46  
    47  If --allow-long-unavailability is set, schema changes affecting a large number of rows (and possibly incurring a longer period of unavailability) will not be rejected.
    48  --ddl-strategy is used to instruct migrations via vreplication, gh-ost or pt-osc with optional parameters.
    49  --migration-context allows the user to specify a custom migration context for online DDL migrations.
    50  If --skip-preflight, SQL goes directly to shards without going through sanity checks.
    51  
    52  The --uuid and --sql flags are repeatable, so they can be passed multiple times to build a list of values.
    53  For --uuid, this is used like "--uuid $first_uuid --uuid $second_uuid".
    54  For --sql, semi-colons and repeated values may be mixed, for example:
    55  
    56  	ApplySchema --sql "CREATE TABLE my_table; CREATE TABLE my_other_table"
    57  	ApplySchema --sql "CREATE TABLE my_table" --sql "CREATE TABLE my_other_table"`,
    58  		DisableFlagsInUseLine: true,
    59  		Args:                  cobra.ExactArgs(1),
    60  		RunE:                  commandApplySchema,
    61  	}
    62  	// GetSchema makes a GetSchema gRPC call to a vtctld.
    63  	GetSchema = &cobra.Command{
    64  		Use:                   "GetSchema [--tables TABLES ...] [--exclude-tables EXCLUDE_TABLES ...] [{--table-names-only | --table-sizes-only}] [--include-views] alias",
    65  		Short:                 "Displays the full schema for a tablet, optionally restricted to the specified tables/views.",
    66  		DisableFlagsInUseLine: true,
    67  		Args:                  cobra.ExactArgs(1),
    68  		RunE:                  commandGetSchema,
    69  	}
    70  	// ReloadSchema makes a ReloadSchema gRPC call to a vtctld.
    71  	ReloadSchema = &cobra.Command{
    72  		Use:                   "ReloadSchema <tablet_alias>",
    73  		Short:                 "Reloads the schema on a remote tablet.",
    74  		DisableFlagsInUseLine: true,
    75  		Args:                  cobra.ExactArgs(1),
    76  		RunE:                  commandReloadSchema,
    77  	}
    78  	// ReloadSchemaKeyspace makes a ReloadSchemaKeyspace gRPC call to a vtctld.
    79  	ReloadSchemaKeyspace = &cobra.Command{
    80  		Use:                   "ReloadSchemaKeyspace [--concurrency=<concurrency>] [--include-primary] <keyspace>",
    81  		Short:                 "Reloads the schema on all tablets in a keyspace. This is done on a best-effort basis.",
    82  		DisableFlagsInUseLine: true,
    83  		Args:                  cobra.ExactArgs(1),
    84  		RunE:                  commandReloadSchemaKeyspace,
    85  	}
    86  	// ReloadSchemaShard makes a ReloadSchemaShard gRPC call to a vtctld.
    87  	ReloadSchemaShard = &cobra.Command{
    88  		Use:                   "ReloadSchemaShard [--concurrency=10] [--include-primary] <keyspace/shard>",
    89  		Short:                 "Reloads the schema on all tablets in a shard. This is done on a best-effort basis.",
    90  		DisableFlagsInUseLine: true,
    91  		Args:                  cobra.ExactArgs(1),
    92  		RunE:                  commandReloadSchemaShard,
    93  	}
    94  )
    95  
    96  var applySchemaOptions = struct {
    97  	AllowLongUnavailability bool
    98  	SQL                     []string
    99  	SQLFile                 string
   100  	DDLStrategy             string
   101  	UUIDList                []string
   102  	MigrationContext        string
   103  	WaitReplicasTimeout     time.Duration
   104  	SkipPreflight           bool
   105  	CallerID                string
   106  }{}
   107  
   108  func commandApplySchema(cmd *cobra.Command, args []string) error {
   109  	var allSQL string
   110  	if applySchemaOptions.SQLFile != "" {
   111  		if len(applySchemaOptions.SQL) != 0 {
   112  			return errors.New("Exactly one of --sql and --sql-file must be specified, not both.") // nolint
   113  		}
   114  
   115  		data, err := os.ReadFile(applySchemaOptions.SQLFile)
   116  		if err != nil {
   117  			return err
   118  		}
   119  
   120  		allSQL = string(data)
   121  	} else {
   122  		allSQL = strings.Join(applySchemaOptions.SQL, ";")
   123  	}
   124  
   125  	parts, err := sqlparser.SplitStatementToPieces(allSQL)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	cli.FinishedParsing(cmd)
   131  
   132  	var cid *vtrpc.CallerID
   133  	if applySchemaOptions.CallerID != "" {
   134  		cid = &vtrpc.CallerID{Principal: applySchemaOptions.CallerID}
   135  	}
   136  
   137  	ks := cmd.Flags().Arg(0)
   138  
   139  	resp, err := client.ApplySchema(commandCtx, &vtctldatapb.ApplySchemaRequest{
   140  		Keyspace:                ks,
   141  		AllowLongUnavailability: applySchemaOptions.AllowLongUnavailability,
   142  		DdlStrategy:             applySchemaOptions.DDLStrategy,
   143  		Sql:                     parts,
   144  		SkipPreflight:           applySchemaOptions.SkipPreflight,
   145  		UuidList:                applySchemaOptions.UUIDList,
   146  		MigrationContext:        applySchemaOptions.MigrationContext,
   147  		WaitReplicasTimeout:     protoutil.DurationToProto(applySchemaOptions.WaitReplicasTimeout),
   148  		CallerId:                cid,
   149  	})
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	fmt.Println(strings.Join(resp.UuidList, "\n"))
   155  	return nil
   156  }
   157  
   158  var getSchemaOptions = struct {
   159  	Tables          []string
   160  	ExcludeTables   []string
   161  	IncludeViews    bool
   162  	TableNamesOnly  bool
   163  	TableSizesOnly  bool
   164  	TableSchemaOnly bool
   165  }{}
   166  
   167  func commandGetSchema(cmd *cobra.Command, args []string) error {
   168  	if getSchemaOptions.TableNamesOnly && getSchemaOptions.TableSizesOnly {
   169  		return errors.New("can only pass one of --table-names-only and --table-sizes-only")
   170  	}
   171  
   172  	alias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0))
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	cli.FinishedParsing(cmd)
   178  
   179  	resp, err := client.GetSchema(commandCtx, &vtctldatapb.GetSchemaRequest{
   180  		TabletAlias:     alias,
   181  		Tables:          getSchemaOptions.Tables,
   182  		ExcludeTables:   getSchemaOptions.ExcludeTables,
   183  		IncludeViews:    getSchemaOptions.IncludeViews,
   184  		TableNamesOnly:  getSchemaOptions.TableNamesOnly,
   185  		TableSizesOnly:  getSchemaOptions.TableSizesOnly,
   186  		TableSchemaOnly: getSchemaOptions.TableSchemaOnly,
   187  	})
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	if getSchemaOptions.TableNamesOnly {
   193  		names := make([]string, len(resp.Schema.TableDefinitions))
   194  
   195  		for i, td := range resp.Schema.TableDefinitions {
   196  			names[i] = td.Name
   197  		}
   198  
   199  		fmt.Printf("%s\n", strings.Join(names, "\n"))
   200  
   201  		return nil
   202  	}
   203  
   204  	data, err := cli.MarshalJSON(resp.Schema)
   205  	if err != nil {
   206  		return err
   207  	}
   208  
   209  	fmt.Printf("%s\n", data)
   210  
   211  	return nil
   212  }
   213  
   214  func commandReloadSchema(cmd *cobra.Command, args []string) error {
   215  	tabletAlias, err := topoproto.ParseTabletAlias(cmd.Flags().Arg(0))
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	cli.FinishedParsing(cmd)
   221  
   222  	_, err = client.ReloadSchema(commandCtx, &vtctldatapb.ReloadSchemaRequest{
   223  		TabletAlias: tabletAlias,
   224  	})
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  var reloadSchemaKeyspaceOptions = struct {
   233  	Concurrency    uint32
   234  	IncludePrimary bool
   235  }{
   236  	Concurrency: 10,
   237  }
   238  
   239  func commandReloadSchemaKeyspace(cmd *cobra.Command, args []string) error {
   240  	cli.FinishedParsing(cmd)
   241  
   242  	logger := logutil.NewConsoleLogger()
   243  	resp, err := client.ReloadSchemaKeyspace(commandCtx, &vtctldatapb.ReloadSchemaKeyspaceRequest{
   244  		Keyspace:       cmd.Flags().Arg(0),
   245  		Concurrency:    reloadSchemaKeyspaceOptions.Concurrency,
   246  		IncludePrimary: reloadSchemaKeyspaceOptions.IncludePrimary,
   247  	})
   248  	if resp != nil {
   249  		for _, e := range resp.Events {
   250  			logutil.LogEvent(logger, e)
   251  		}
   252  	}
   253  
   254  	return err
   255  }
   256  
   257  var reloadSchemaShardOptions = struct {
   258  	Concurrency    uint32
   259  	IncludePrimary bool
   260  }{
   261  	Concurrency: 10,
   262  }
   263  
   264  func commandReloadSchemaShard(cmd *cobra.Command, args []string) error {
   265  	keyspace, shard, err := topoproto.ParseKeyspaceShard(cmd.Flags().Arg(0))
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	cli.FinishedParsing(cmd)
   271  
   272  	logger := logutil.NewConsoleLogger()
   273  	resp, err := client.ReloadSchemaShard(commandCtx, &vtctldatapb.ReloadSchemaShardRequest{
   274  		Keyspace:       keyspace,
   275  		Shard:          shard,
   276  		Concurrency:    reloadSchemaShardOptions.Concurrency,
   277  		IncludePrimary: reloadSchemaShardOptions.IncludePrimary,
   278  	})
   279  	if resp != nil {
   280  		for _, e := range resp.Events {
   281  			logutil.LogEvent(logger, e)
   282  		}
   283  	}
   284  
   285  	return err
   286  }
   287  
   288  func init() {
   289  	ApplySchema.Flags().BoolVar(&applySchemaOptions.AllowLongUnavailability, "allow-long-unavailability", false, "Allow large schema changes which incur a longer unavailability of the database.")
   290  	ApplySchema.Flags().StringVar(&applySchemaOptions.DDLStrategy, "ddl-strategy", string(schema.DDLStrategyDirect), "Online DDL strategy, compatible with @@ddl_strategy session variable (examples: 'gh-ost', 'pt-osc', 'gh-ost --max-load=Threads_running=100'.")
   291  	ApplySchema.Flags().StringSliceVar(&applySchemaOptions.UUIDList, "uuid", nil, "Optional, comma-delimited, repeatable, explicit UUIDs for migration. If given, must match number of DDL changes.")
   292  	ApplySchema.Flags().StringVar(&applySchemaOptions.MigrationContext, "migration-context", "", "For Online DDL, optionally supply a custom unique string used as context for the migration(s) in this command. By default a unique context is auto-generated by Vitess.")
   293  	ApplySchema.Flags().DurationVar(&applySchemaOptions.WaitReplicasTimeout, "wait-replicas-timeout", wrangler.DefaultWaitReplicasTimeout, "Amount of time to wait for replicas to receive the schema change via replication.")
   294  	ApplySchema.Flags().BoolVar(&applySchemaOptions.SkipPreflight, "skip-preflight", false, "Skip pre-apply schema checks, and directly forward schema change query to shards.")
   295  	ApplySchema.Flags().StringVar(&applySchemaOptions.CallerID, "caller-id", "", "Effective caller ID used for the operation and should map to an ACL name which grants this identity the necessary permissions to perform the operation (this is only necessary when strict table ACLs are used).")
   296  	ApplySchema.Flags().StringArrayVar(&applySchemaOptions.SQL, "sql", nil, "Semicolon-delimited, repeatable SQL commands to apply. Exactly one of --sql|--sql-file is required.")
   297  	ApplySchema.Flags().StringVar(&applySchemaOptions.SQLFile, "sql-file", "", "Path to a file containing semicolon-delimited SQL commands to apply. Exactly one of --sql|--sql-file is required.")
   298  
   299  	Root.AddCommand(ApplySchema)
   300  
   301  	GetSchema.Flags().StringSliceVar(&getSchemaOptions.Tables, "tables", nil, "List of tables to display the schema for. Each is either an exact match, or a regular expression of the form `/regexp/`.")
   302  	GetSchema.Flags().StringSliceVar(&getSchemaOptions.ExcludeTables, "exclude-tables", nil, "List of tables to exclude from the result. Each is either an exact match, or a regular expression of the form `/regexp/`.")
   303  	GetSchema.Flags().BoolVar(&getSchemaOptions.IncludeViews, "include-views", false, "Includes views in the output in addition to base tables.")
   304  	GetSchema.Flags().BoolVarP(&getSchemaOptions.TableNamesOnly, "table-names-only", "n", false, "Display only table names in the result.")
   305  	GetSchema.Flags().BoolVarP(&getSchemaOptions.TableSizesOnly, "table-sizes-only", "s", false, "Display only size information for matching tables. Ignored if --table-names-only is set.")
   306  	GetSchema.Flags().BoolVarP(&getSchemaOptions.TableSchemaOnly, "table-schema-only", "", false, "Skip introspecting columns and fields metadata.")
   307  
   308  	Root.AddCommand(GetSchema)
   309  
   310  	Root.AddCommand(ReloadSchema)
   311  
   312  	ReloadSchemaKeyspace.Flags().Uint32Var(&reloadSchemaKeyspaceOptions.Concurrency, "concurrency", 10, "Number of tablets to reload in parallel. Set to zero for unbounded concurrency.")
   313  	ReloadSchemaKeyspace.Flags().BoolVar(&reloadSchemaKeyspaceOptions.IncludePrimary, "include-primary", false, "Also reload the primary tablets.")
   314  	Root.AddCommand(ReloadSchemaKeyspace)
   315  
   316  	ReloadSchemaShard.Flags().Uint32Var(&reloadSchemaShardOptions.Concurrency, "concurrency", 10, "Number of tablets to reload in parallel. Set to zero for unbounded concurrency.")
   317  	ReloadSchemaShard.Flags().BoolVar(&reloadSchemaShardOptions.IncludePrimary, "include-primary", false, "Also reload the primary tablet.")
   318  	Root.AddCommand(ReloadSchemaShard)
   319  }