blob: 2e23090bb4f0f3de4cc55ef9f5d152efd53b774a [file] [log] [blame]
package commands
import (
"fmt"
"io"
"strconv"
"strings"
"v.io/v23/context"
"v.io/v23/syncbase"
"v.io/x/lib/cmdline"
"v.io/x/ref/cmd/sb/internal/writer"
)
var flagFormat = "table"
func DumpDB(ctx *context.T, env *cmdline.Env, args[] string) error {
db := ctx.Value("database")
return dumpDatabase(ctx, env.Stdout, db.(syncbase.Database));
}
func dumpDatabase(ctx *context.T, w io.Writer, d syncbase.Database) error {
var errors []error
if err := dumpCollections(ctx, w, d); err != nil {
errors = append(errors, fmt.Errorf("failed dumping collections: %v", err))
}
if err := dumpSyncgroups(ctx, w, d); err != nil {
errors = append(errors, fmt.Errorf("failed dumping syncgroups: %v", err))
}
return mergeErrors(errors)
}
func dumpCollections(ctx *context.T, w io.Writer, d syncbase.Database) error {
collections, err := d.ListCollections(ctx)
if err != nil {
return fmt.Errorf("failed listing collections: %v", err)
}
var errs []error
for _, collection := range collections {
fmt.Fprintf(w, "collection: %v\n", collection)
// TODO(ivanpi): Queries currently support only the default user blessing.
if err := queryExec(ctx, w, d, fmt.Sprintf("select k, v from %s", collection.Name)); err != nil {
errs = append(errs, fmt.Errorf("> %v: %v", collection, err))
}
}
if len(errs) > 0 {
return fmt.Errorf("failed dumping %d of %d collections:\n%v", len(errs), len(collections), mergeErrors(errs))
}
return nil
}
func dumpSyncgroups(ctx *context.T, w io.Writer, d syncbase.Database) error {
sgIds, err := d.ListSyncgroups(ctx)
if err != nil {
return fmt.Errorf("failed listing syncgroups: %v", err)
}
var errs []error
for _, sgId := range sgIds {
fmt.Fprintf(w, "syncgroup: %+v\n", sgId)
if spec, version, err := d.SyncgroupForId(sgId).GetSpec(ctx); err != nil {
errs = append(errs, err)
} else {
fmt.Fprintf(w, "%+v (version: \"%s\")\n", spec, version)
}
}
if len(errs) > 0 {
return fmt.Errorf("failed dumping %d of %d syncgroups:\n%v", len(errs), len(sgIds), mergeErrors(errs))
}
return nil
}
func mergeErrors(errs []error) error {
if len(errs) == 0 {
return nil
}
err := errs[0]
for _, e := range errs[1:] {
err = fmt.Errorf("%v\n%v", err, e)
}
return err
}
// Split an error message into an offset and the remaining (i.e., rhs of offset) message.
// The convention for syncql is "<module><optional-rpc>[offset]<remaining-message>".
func splitError(err error) (int64, string) {
errMsg := err.Error()
idx1 := strings.Index(errMsg, "[")
idx2 := strings.Index(errMsg, "]")
if idx1 == -1 || idx2 == -1 {
return 0, errMsg
}
offsetString := errMsg[idx1+1 : idx2]
offset, err := strconv.ParseInt(offsetString, 10, 64)
if err != nil {
return 0, errMsg
}
return offset, errMsg[idx2+1:]
}
func queryExec(ctx *context.T, w io.Writer, d syncbase.Database, q string) error {
columnNames, rs, err := d.Exec(ctx, q)
if err != nil {
off, msg := splitError(err)
return fmt.Errorf("\n%s\n%s^\n%d: %s", q, strings.Repeat(" ", int(off)), off+1, msg)
}
return writer.NewTableWriter(w).Write(columnNames, rs)
//return flagFormat.NewWriter(w).Write(columnNames, rs)
}