Skip to content

Commit

Permalink
backupccl: support SHOW BACKUP [RANGES|FILES]
Browse files Browse the repository at this point in the history
This makes debugging a broken backup substantially easier.

Release note (enterprise change): Introduce SHOW BACKUP RANGES and SHOW
BACKUP FILES, which show details about the ranges and files,
respectively, which comprise a backup.
  • Loading branch information
benesch committed Jun 6, 2018
1 parent f85a3ce commit 74b0bf7
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 20 deletions.
2 changes: 2 additions & 0 deletions docs/generated/sql/bnf/show_backup.bnf
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
show_backup_stmt ::=
'SHOW' 'BACKUP' location
| 'SHOW' 'BACKUP' 'RANGES' location
| 'SHOW' 'BACKUP' 'FILES' location
4 changes: 4 additions & 0 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ use_stmt ::=

show_backup_stmt ::=
'SHOW' 'BACKUP' string_or_placeholder
| 'SHOW' 'BACKUP' 'RANGES' string_or_placeholder
| 'SHOW' 'BACKUP' 'FILES' string_or_placeholder

show_columns_stmt ::=
'SHOW' 'COLUMNS' 'FROM' table_name
Expand Down Expand Up @@ -680,6 +682,7 @@ unreserved_keyword ::=
| 'EXPERIMENTAL_REPLICA'
| 'EXPLAIN'
| 'EXPORT'
| 'FILES'
| 'FILTER'
| 'FIRST'
| 'FLOAT4'
Expand Down Expand Up @@ -756,6 +759,7 @@ unreserved_keyword ::=
| 'QUERIES'
| 'QUERY'
| 'RANGE'
| 'RANGES'
| 'READ'
| 'RECURSIVE'
| 'REF'
Expand Down
107 changes: 94 additions & 13 deletions pkg/ccl/backupccl/show.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 The Cockroach Authors.
// Copyright 2018 The Cockroach Authors.
//
// Licensed as a CockroachDB Enterprise file under the Cockroach Community
// License (the "License"); you may not use this file except in compliance with
Expand Down Expand Up @@ -46,14 +46,17 @@ func showBackupPlanHook(
if err != nil {
return nil, nil, nil, err
}
header := sqlbase.ResultColumns{
{Name: "database", Typ: types.String},
{Name: "table", Typ: types.String},
{Name: "start_time", Typ: types.Timestamp},
{Name: "end_time", Typ: types.Timestamp},
{Name: "size_bytes", Typ: types.Int},
{Name: "rows", Typ: types.Int},

var shower backupShower
switch backup.Details {
case tree.BackupRangeDetails:
shower = backupShowerRanges
case tree.BackupFileDetails:
shower = backupShowerFiles
default:
shower = backupShowerDefault
}

fn := func(ctx context.Context, _ []sql.PlanNode, resultsCh chan<- tree.Datums) error {
// TODO(dan): Move this span into sql.
ctx, span := tracing.ChildSpan(ctx, stmt.StatementTag())
Expand All @@ -67,6 +70,36 @@ func showBackupPlanHook(
if err != nil {
return err
}

for _, row := range shower.fn(desc) {
select {
case <-ctx.Done():
return ctx.Err()
case resultsCh <- row:
}
}
return nil
}

return fn, shower.header, nil, nil
}

type backupShower struct {
header sqlbase.ResultColumns
fn func(BackupDescriptor) []tree.Datums
}

var backupShowerDefault = backupShower{
header: sqlbase.ResultColumns{
{Name: "database", Typ: types.String},
{Name: "table", Typ: types.String},
{Name: "start_time", Typ: types.Timestamp},
{Name: "end_time", Typ: types.Timestamp},
{Name: "size_bytes", Typ: types.Int},
{Name: "rows", Typ: types.Int},
},

fn: func(desc BackupDescriptor) []tree.Datums {
descs := make(map[sqlbase.ID]string)
for _, descriptor := range desc.Descriptors {
if database := descriptor.GetDatabase(); database != nil {
Expand Down Expand Up @@ -94,22 +127,70 @@ func showBackupPlanHook(
if desc.StartTime.WallTime != 0 {
start = tree.MakeDTimestamp(timeutil.Unix(0, desc.StartTime.WallTime), time.Nanosecond)
}
var rows []tree.Datums
for _, descriptor := range desc.Descriptors {
if table := descriptor.GetTable(); table != nil {
dbName := descs[table.ParentID]
resultsCh <- tree.Datums{
rows = append(rows, tree.Datums{
tree.NewDString(dbName),
tree.NewDString(table.Name),
start,
tree.MakeDTimestamp(timeutil.Unix(0, desc.EndTime.WallTime), time.Nanosecond),
tree.NewDInt(tree.DInt(descSizes[table.ID].DataSize)),
tree.NewDInt(tree.DInt(descSizes[table.ID].Rows)),
}
})
}
}
return nil
}
return fn, header, nil, nil
return rows
},
}

var backupShowerRanges = backupShower{
header: sqlbase.ResultColumns{
{Name: "start_pretty", Typ: types.String},
{Name: "end_pretty", Typ: types.String},
{Name: "start_key", Typ: types.Bytes},
{Name: "end_key", Typ: types.Bytes},
},

fn: func(desc BackupDescriptor) (rows []tree.Datums) {
for _, span := range desc.Spans {
rows = append(rows, tree.Datums{
tree.NewDString(span.Key.String()),
tree.NewDString(span.EndKey.String()),
tree.NewDBytes(tree.DBytes(span.Key)),
tree.NewDBytes(tree.DBytes(span.EndKey)),
})
}
return rows
},
}

var backupShowerFiles = backupShower{
header: sqlbase.ResultColumns{
{Name: "path", Typ: types.String},
{Name: "start_pretty", Typ: types.String},
{Name: "end_pretty", Typ: types.String},
{Name: "start_key", Typ: types.Bytes},
{Name: "end_key", Typ: types.Bytes},
{Name: "size_bytes", Typ: types.Int},
{Name: "rows", Typ: types.Int},
},

fn: func(desc BackupDescriptor) (rows []tree.Datums) {
for _, file := range desc.Files {
rows = append(rows, tree.Datums{
tree.NewDString(file.Path),
tree.NewDString(file.Span.Key.String()),
tree.NewDString(file.Span.EndKey.String()),
tree.NewDBytes(tree.DBytes(file.Span.Key)),
tree.NewDBytes(tree.DBytes(file.Span.EndKey)),
tree.NewDInt(tree.DInt(file.EntryCounts.DataSize)),
tree.NewDInt(tree.DInt(file.EntryCounts.Rows)),
})
}
return rows
},
}

func init() {
Expand Down
42 changes: 40 additions & 2 deletions pkg/ccl/backupccl/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ package backupccl_test

import (
"database/sql/driver"
"fmt"
"regexp"
"testing"
"time"

"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
)
Expand All @@ -21,10 +25,10 @@ func TestShowBackup(t *testing.T) {
defer leaktest.AfterTest(t)()

const numAccounts = 11
_, _, sqlDB, _, cleanupFn := backupRestoreTestSetup(t, singleNode, numAccounts, initNone)
_, tc, sqlDB, _, cleanupFn := backupRestoreTestSetup(t, singleNode, numAccounts, initNone)
defer cleanupFn()

full, inc := localFoo+"/full", localFoo+"/inc"
full, inc, details := localFoo+"/full", localFoo+"/inc", localFoo+"/details"

beforeFull := timeutil.Now()
sqlDB.Exec(t, `BACKUP data.bank TO $1`, full)
Expand Down Expand Up @@ -78,4 +82,38 @@ func TestShowBackup(t *testing.T) {
if expected := affectedRows * 2; rows != uint64(expected) {
t.Errorf("expected %d got: %d", expected, rows)
}

sqlDB.Exec(t, `CREATE TABLE data.details1 (c INT PRIMARY KEY)`)
sqlDB.Exec(t, `INSERT INTO data.details1 (SELECT generate_series(1, 100))`)
sqlDB.Exec(t, `ALTER TABLE data.details1 SPLIT AT VALUES (1), (42)`)
sqlDB.Exec(t, `CREATE TABLE data.details2()`)
sqlDB.Exec(t, `BACKUP data.details1, data.details2 TO $1;`, details)

details1Desc := sqlbase.GetTableDescriptor(tc.Server(0).DB(), "data", "details1")
details2Desc := sqlbase.GetTableDescriptor(tc.Server(0).DB(), "data", "details2")
details1Key := roachpb.Key(sqlbase.MakeIndexKeyPrefix(details1Desc, details1Desc.PrimaryIndex.ID))
details2Key := roachpb.Key(sqlbase.MakeIndexKeyPrefix(details2Desc, details2Desc.PrimaryIndex.ID))

sqlDB.CheckQueryResults(t, fmt.Sprintf(`SHOW BACKUP RANGES '%s'`, details), [][]string{
{"/Table/54/1", "/Table/54/2", string(details1Key), string(details1Key.PrefixEnd())},
{"/Table/55/1", "/Table/55/2", string(details2Key), string(details2Key.PrefixEnd())},
})

var showFiles = fmt.Sprintf(`SELECT start_pretty, end_pretty, size_bytes, rows
FROM [SHOW BACKUP FILES '%s']`, details)
sqlDB.CheckQueryResults(t, showFiles, [][]string{
{"/Table/54/1/1", "/Table/54/1/42", "369", "41"},
{"/Table/54/1/42", "/Table/54/2", "531", "59"},
})
sstMatcher := regexp.MustCompile(`\d+\.sst`)
pathRows := sqlDB.QueryStr(t, `SELECT path FROM [SHOW BACKUP FILES $1]`, details)
for _, row := range pathRows {
path := row[0]
if matched := sstMatcher.MatchString(path); !matched {
t.Errorf("malformatted path in SHOW BACKUP FILES: %s", path)
}
}
if len(pathRows) != 2 {
t.Fatalf("expected 2 files, but got %d", len(pathRows))
}
}
2 changes: 2 additions & 0 deletions pkg/sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,8 @@ func TestParse(t *testing.T) {
{`BACKUP TABLE foo TO 'bar'`},
{`BACKUP TABLE foo.foo, baz.baz TO 'bar'`},
{`SHOW BACKUP 'bar'`},
{`SHOW BACKUP RANGES 'bar'`},
{`SHOW BACKUP FILES 'bar'`},
{`BACKUP TABLE foo TO 'bar' AS OF SYSTEM TIME '1' INCREMENTAL FROM 'baz'`},
{`BACKUP TABLE foo TO $1 INCREMENTAL FROM 'bar', $2, 'baz'`},
{`BACKUP DATABASE foo TO 'bar'`},
Expand Down
28 changes: 24 additions & 4 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ func newNameFromStr(s string) *tree.Name {
%token <str> EXPERIMENTAL_AUDIT
%token <str> EXPLAIN EXPORT EXTRACT EXTRACT_DURATION

%token <str> FALSE FAMILY FETCH FETCHVAL FETCHTEXT FETCHVAL_PATH FETCHTEXT_PATH FILTER
%token <str> FALSE FAMILY FETCH FETCHVAL FETCHTEXT FETCHVAL_PATH FETCHTEXT_PATH
%token <str> FILES FILTER
%token <str> FIRST FLOAT FLOAT4 FLOAT8 FLOORDIV FOLLOWING FOR FORCE_INDEX FOREIGN FROM FULL

%token <str> GIN GRANT GRANTS GREATEST GROUP GROUPING
Expand Down Expand Up @@ -511,7 +512,7 @@ func newNameFromStr(s string) *tree.Name {

%token <str> QUERIES QUERY

%token <str> RANGE READ REAL RECURSIVE REF REFERENCES
%token <str> RANGE RANGES READ REAL RECURSIVE REF REFERENCES
%token <str> REGCLASS REGPROC REGPROCEDURE REGNAMESPACE REGTYPE
%token <str> REMOVE_PATH RENAME REPEATABLE
%token <str> RELEASE RESET RESTORE RESTRICT RESUME RETURNING REVOKE RIGHT
Expand Down Expand Up @@ -2839,12 +2840,29 @@ show_histogram_stmt:

// %Help: SHOW BACKUP - list backup contents
// %Category: CCL
// %Text: SHOW BACKUP <location>
// %Text: SHOW BACKUP [FILES|RANGES] <location>
// %SeeAlso: WEBDOCS/show-backup.html
show_backup_stmt:
SHOW BACKUP string_or_placeholder
{
$$.val = &tree.ShowBackup{Path: $3.expr()}
$$.val = &tree.ShowBackup{
Details: tree.BackupDefaultDetails,
Path: $3.expr(),
}
}
| SHOW BACKUP RANGES string_or_placeholder
{
$$.val = &tree.ShowBackup{
Details: tree.BackupRangeDetails,
Path: $4.expr(),
}
}
| SHOW BACKUP FILES string_or_placeholder
{
$$.val = &tree.ShowBackup{
Details: tree.BackupFileDetails,
Path: $4.expr(),
}
}
| SHOW BACKUP error // SHOW HELP: SHOW BACKUP

Expand Down Expand Up @@ -7921,6 +7939,7 @@ unreserved_keyword:
| EXPERIMENTAL_REPLICA
| EXPLAIN
| EXPORT
| FILES
| FILTER
| FIRST
| FLOAT4
Expand Down Expand Up @@ -7997,6 +8016,7 @@ unreserved_keyword:
| QUERIES
| QUERY
| RANGE
| RANGES
| READ
| RECURSIVE
| REF
Expand Down
21 changes: 20 additions & 1 deletion pkg/sql/sem/tree/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,33 @@ func (node *ShowClusterSetting) Format(ctx *FmtCtx) {
ctx.WriteString(node.Name)
}

// BackupDetails represents the type of details to display for a SHOW BACKUP
// statement.
type BackupDetails int

const (
// BackupDefaultDetails identifies a bare SHOW BACKUP statement.
BackupDefaultDetails BackupDetails = iota
// BackupRangeDetails identifies a SHOW BACKUP RANGES statement.
BackupRangeDetails
// BackupFileDetails identifies a SHOW BACKUP FILES statement.
BackupFileDetails
)

// ShowBackup represents a SHOW BACKUP statement.
type ShowBackup struct {
Path Expr
Path Expr
Details BackupDetails
}

// Format implements the NodeFormatter interface.
func (node *ShowBackup) Format(ctx *FmtCtx) {
ctx.WriteString("SHOW BACKUP ")
if node.Details == BackupRangeDetails {
ctx.WriteString("RANGES ")
} else if node.Details == BackupFileDetails {
ctx.WriteString("FILES ")
}
ctx.FormatNode(node.Path)
}

Expand Down

0 comments on commit 74b0bf7

Please sign in to comment.