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 }