github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/backupccl/show.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package backupccl
    10  
    11  import (
    12  	"context"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/ccl/storageccl"
    17  	"github.com/cockroachdb/cockroach/pkg/ccl/utilccl"
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/sql"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    23  	"github.com/cockroachdb/cockroach/pkg/storage/cloud"
    24  	"github.com/cockroachdb/cockroach/pkg/util/encoding"
    25  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    26  	"github.com/cockroachdb/cockroach/pkg/util/log"
    27  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    28  	"github.com/cockroachdb/cockroach/pkg/util/tracing"
    29  	"github.com/cockroachdb/errors"
    30  )
    31  
    32  // showBackupPlanHook implements PlanHookFn.
    33  func showBackupPlanHook(
    34  	ctx context.Context, stmt tree.Statement, p sql.PlanHookState,
    35  ) (sql.PlanHookRowFn, sqlbase.ResultColumns, []sql.PlanNode, bool, error) {
    36  	backup, ok := stmt.(*tree.ShowBackup)
    37  	if !ok {
    38  		return nil, nil, nil, false, nil
    39  	}
    40  
    41  	if err := utilccl.CheckEnterpriseEnabled(
    42  		p.ExecCfg().Settings, p.ExecCfg().ClusterID(), p.ExecCfg().Organization(), "SHOW BACKUP",
    43  	); err != nil {
    44  		return nil, nil, nil, false, err
    45  	}
    46  
    47  	if err := p.RequireAdminRole(ctx, "SHOW BACKUP"); err != nil {
    48  		return nil, nil, nil, false, err
    49  	}
    50  
    51  	toFn, err := p.TypeAsString(ctx, backup.Path, "SHOW BACKUP")
    52  	if err != nil {
    53  		return nil, nil, nil, false, err
    54  	}
    55  
    56  	expected := map[string]sql.KVStringOptValidate{
    57  		backupOptEncPassphrase:  sql.KVStringOptRequireValue,
    58  		backupOptWithPrivileges: sql.KVStringOptRequireNoValue,
    59  	}
    60  	optsFn, err := p.TypeAsStringOpts(ctx, backup.Options, expected)
    61  	if err != nil {
    62  		return nil, nil, nil, false, err
    63  	}
    64  	opts, err := optsFn()
    65  	if err != nil {
    66  		return nil, nil, nil, false, err
    67  	}
    68  
    69  	var shower backupShower
    70  	switch backup.Details {
    71  	case tree.BackupRangeDetails:
    72  		shower = backupShowerRanges
    73  	case tree.BackupFileDetails:
    74  		shower = backupShowerFiles
    75  	default:
    76  		shower = backupShowerDefault(ctx, p, backup.ShouldIncludeSchemas, opts)
    77  	}
    78  
    79  	fn := func(ctx context.Context, _ []sql.PlanNode, resultsCh chan<- tree.Datums) error {
    80  		// TODO(dan): Move this span into sql.
    81  		ctx, span := tracing.ChildSpan(ctx, stmt.StatementTag())
    82  		defer tracing.FinishSpan(span)
    83  
    84  		str, err := toFn()
    85  		if err != nil {
    86  			return err
    87  		}
    88  
    89  		store, err := p.ExecCfg().DistSQLSrv.ExternalStorageFromURI(ctx, str)
    90  		if err != nil {
    91  			return errors.Wrapf(err, "make storage")
    92  		}
    93  		defer store.Close()
    94  
    95  		var encryption *roachpb.FileEncryptionOptions
    96  		if passphrase, ok := opts[backupOptEncPassphrase]; ok {
    97  			opts, err := readEncryptionOptions(ctx, store)
    98  			if err != nil {
    99  				return err
   100  			}
   101  			encryptionKey := storageccl.GenerateKey([]byte(passphrase), opts.Salt)
   102  			encryption = &roachpb.FileEncryptionOptions{Key: encryptionKey}
   103  		}
   104  
   105  		incPaths, err := findPriorBackups(ctx, store)
   106  		if err != nil {
   107  			if errors.Is(err, cloud.ErrListingUnsupported) {
   108  				// If we do not support listing, we have to just assume there are none
   109  				// and show the specified base.
   110  				log.Warningf(ctx, "storage sink %T does not support listing, only resolving the base backup", store)
   111  				incPaths = nil
   112  			} else {
   113  				return err
   114  			}
   115  		}
   116  
   117  		manifests := make([]BackupManifest, len(incPaths)+1)
   118  		manifests[0], err = readBackupManifestFromStore(ctx, store, encryption)
   119  		if err != nil {
   120  			return err
   121  		}
   122  
   123  		for i := range incPaths {
   124  			m, err := readBackupManifest(ctx, store, incPaths[i], encryption)
   125  			if err != nil {
   126  				return err
   127  			}
   128  			// Blank the stats to prevent memory blowup.
   129  			m.Statistics = nil
   130  			manifests[i+1] = m
   131  		}
   132  
   133  		// If we are restoring a backup with old-style foreign keys, skip over the
   134  		// FKs for which we can't resolve the cross-table references. We can't
   135  		// display them anyway, because we don't have the referenced table names,
   136  		// etc.
   137  		if err := maybeUpgradeTableDescsInBackupManifests(
   138  			ctx, manifests, p.ExecCfg().Codec, true, /*skipFKsWithNoMatchingTable*/
   139  		); err != nil {
   140  			return err
   141  		}
   142  
   143  		datums, err := shower.fn(manifests)
   144  		if err != nil {
   145  			return err
   146  		}
   147  		for _, row := range datums {
   148  			select {
   149  			case <-ctx.Done():
   150  				return ctx.Err()
   151  			case resultsCh <- row:
   152  			}
   153  		}
   154  		return nil
   155  	}
   156  
   157  	return fn, shower.header, nil, false, nil
   158  }
   159  
   160  type backupShower struct {
   161  	header sqlbase.ResultColumns
   162  	fn     func([]BackupManifest) ([]tree.Datums, error)
   163  }
   164  
   165  func backupShowerHeaders(showSchemas bool, opts map[string]string) sqlbase.ResultColumns {
   166  	baseHeaders := sqlbase.ResultColumns{
   167  		{Name: "database_name", Typ: types.String},
   168  		{Name: "table_name", Typ: types.String},
   169  		{Name: "start_time", Typ: types.Timestamp},
   170  		{Name: "end_time", Typ: types.Timestamp},
   171  		{Name: "size_bytes", Typ: types.Int},
   172  		{Name: "rows", Typ: types.Int},
   173  		{Name: "is_full_cluster", Typ: types.Bool},
   174  	}
   175  	if showSchemas {
   176  		baseHeaders = append(baseHeaders, sqlbase.ResultColumn{Name: "create_statement", Typ: types.String})
   177  	}
   178  	if _, shouldShowPrivleges := opts[backupOptWithPrivileges]; shouldShowPrivleges {
   179  		baseHeaders = append(baseHeaders, sqlbase.ResultColumn{Name: "privileges", Typ: types.String})
   180  	}
   181  	return baseHeaders
   182  }
   183  
   184  func backupShowerDefault(
   185  	ctx context.Context, p sql.PlanHookState, showSchemas bool, opts map[string]string,
   186  ) backupShower {
   187  	return backupShower{
   188  		header: backupShowerHeaders(showSchemas, opts),
   189  		fn: func(manifests []BackupManifest) ([]tree.Datums, error) {
   190  			var rows []tree.Datums
   191  			for _, manifest := range manifests {
   192  				descs := make(map[sqlbase.ID]string)
   193  				for _, descriptor := range manifest.Descriptors {
   194  					if database := descriptor.GetDatabase(); database != nil {
   195  						if _, ok := descs[database.ID]; !ok {
   196  							descs[database.ID] = database.Name
   197  						}
   198  					}
   199  				}
   200  				descSizes := make(map[sqlbase.ID]RowCount)
   201  				for _, file := range manifest.Files {
   202  					// TODO(dan): This assumes each file in the backup only contains
   203  					// data from a single table, which is usually but not always
   204  					// correct. It does not account for interleaved tables or if a
   205  					// BACKUP happened to catch a newly created table that hadn't yet
   206  					// been split into its own range.
   207  					_, tableID, err := encoding.DecodeUvarintAscending(file.Span.Key)
   208  					if err != nil {
   209  						continue
   210  					}
   211  					s := descSizes[sqlbase.ID(tableID)]
   212  					s.add(file.EntryCounts)
   213  					descSizes[sqlbase.ID(tableID)] = s
   214  				}
   215  				start := tree.DNull
   216  				end, err := tree.MakeDTimestamp(timeutil.Unix(0, manifest.EndTime.WallTime), time.Nanosecond)
   217  				if err != nil {
   218  					return nil, err
   219  				}
   220  				if manifest.StartTime.WallTime != 0 {
   221  					start, err = tree.MakeDTimestamp(timeutil.Unix(0, manifest.StartTime.WallTime), time.Nanosecond)
   222  					if err != nil {
   223  						return nil, err
   224  					}
   225  				}
   226  				var row tree.Datums
   227  				for _, descriptor := range manifest.Descriptors {
   228  					if table := descriptor.Table(hlc.Timestamp{}); table != nil {
   229  						dbName := descs[table.ParentID]
   230  						row = tree.Datums{
   231  							tree.NewDString(dbName),
   232  							tree.NewDString(table.Name),
   233  							start,
   234  							end,
   235  							tree.NewDInt(tree.DInt(descSizes[table.ID].DataSize)),
   236  							tree.NewDInt(tree.DInt(descSizes[table.ID].Rows)),
   237  							tree.MakeDBool(manifest.DescriptorCoverage == tree.AllDescriptors),
   238  						}
   239  						if showSchemas {
   240  							displayOptions := sql.ShowCreateDisplayOptions{
   241  								FKDisplayMode:  sql.OmitMissingFKClausesFromCreate,
   242  								IgnoreComments: true,
   243  							}
   244  							schema, err := p.ShowCreate(ctx, dbName, manifest.Descriptors, table, displayOptions)
   245  							if err != nil {
   246  								continue
   247  							}
   248  							row = append(row, tree.NewDString(schema))
   249  						}
   250  						if _, shouldShowPrivileges := opts[backupOptWithPrivileges]; shouldShowPrivileges {
   251  							row = append(row, tree.NewDString(showPrivileges(descriptor)))
   252  						}
   253  						rows = append(rows, row)
   254  					}
   255  				}
   256  			}
   257  			return rows, nil
   258  		},
   259  	}
   260  }
   261  
   262  func showPrivileges(descriptor sqlbase.Descriptor) string {
   263  	var privStringBuilder strings.Builder
   264  	var privDesc *sqlbase.PrivilegeDescriptor
   265  	if db := descriptor.GetDatabase(); db != nil {
   266  		privDesc = db.GetPrivileges()
   267  	} else if table := descriptor.Table(hlc.Timestamp{}); table != nil {
   268  		privDesc = table.GetPrivileges()
   269  	}
   270  	if privDesc == nil {
   271  		return ""
   272  	}
   273  	for _, userPriv := range privDesc.Show() {
   274  		user := userPriv.User
   275  		privs := userPriv.Privileges
   276  		privStringBuilder.WriteString("GRANT ")
   277  		if len(privs) == 0 {
   278  			continue
   279  		}
   280  
   281  		for j, priv := range privs {
   282  			if j != 0 {
   283  				privStringBuilder.WriteString(", ")
   284  			}
   285  			privStringBuilder.WriteString(priv)
   286  		}
   287  		privStringBuilder.WriteString(" ON ")
   288  		privStringBuilder.WriteString(descriptor.GetName())
   289  		privStringBuilder.WriteString(" TO ")
   290  		privStringBuilder.WriteString(user)
   291  		privStringBuilder.WriteString("; ")
   292  	}
   293  
   294  	return privStringBuilder.String()
   295  }
   296  
   297  var backupShowerRanges = backupShower{
   298  	header: sqlbase.ResultColumns{
   299  		{Name: "start_pretty", Typ: types.String},
   300  		{Name: "end_pretty", Typ: types.String},
   301  		{Name: "start_key", Typ: types.Bytes},
   302  		{Name: "end_key", Typ: types.Bytes},
   303  	},
   304  
   305  	fn: func(manifests []BackupManifest) (rows []tree.Datums, err error) {
   306  		for _, manifest := range manifests {
   307  			for _, span := range manifest.Spans {
   308  				rows = append(rows, tree.Datums{
   309  					tree.NewDString(span.Key.String()),
   310  					tree.NewDString(span.EndKey.String()),
   311  					tree.NewDBytes(tree.DBytes(span.Key)),
   312  					tree.NewDBytes(tree.DBytes(span.EndKey)),
   313  				})
   314  			}
   315  		}
   316  		return rows, nil
   317  	},
   318  }
   319  
   320  var backupShowerFiles = backupShower{
   321  	header: sqlbase.ResultColumns{
   322  		{Name: "path", Typ: types.String},
   323  		{Name: "start_pretty", Typ: types.String},
   324  		{Name: "end_pretty", Typ: types.String},
   325  		{Name: "start_key", Typ: types.Bytes},
   326  		{Name: "end_key", Typ: types.Bytes},
   327  		{Name: "size_bytes", Typ: types.Int},
   328  		{Name: "rows", Typ: types.Int},
   329  	},
   330  
   331  	fn: func(manifests []BackupManifest) (rows []tree.Datums, err error) {
   332  		for _, manifest := range manifests {
   333  			for _, file := range manifest.Files {
   334  				rows = append(rows, tree.Datums{
   335  					tree.NewDString(file.Path),
   336  					tree.NewDString(file.Span.Key.String()),
   337  					tree.NewDString(file.Span.EndKey.String()),
   338  					tree.NewDBytes(tree.DBytes(file.Span.Key)),
   339  					tree.NewDBytes(tree.DBytes(file.Span.EndKey)),
   340  					tree.NewDInt(tree.DInt(file.EntryCounts.DataSize)),
   341  					tree.NewDInt(tree.DInt(file.EntryCounts.Rows)),
   342  				})
   343  			}
   344  		}
   345  		return rows, nil
   346  	},
   347  }
   348  
   349  func init() {
   350  	sql.AddPlanHook(showBackupPlanHook)
   351  }