mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-07 18:49:08 -06:00
Add noms root command for getting and setting root of entire db (#2992)
* Add `noms root` command for getting and setting root of entire db * fix missing import * rebase, review feedback * review feedback * moar review
This commit is contained in:
@@ -23,6 +23,7 @@ var commands = []*util.Command{
|
||||
nomsLog,
|
||||
nomsMerge,
|
||||
nomsMigrate,
|
||||
nomsRoot,
|
||||
nomsServe,
|
||||
nomsShow,
|
||||
nomsSync,
|
||||
|
||||
112
cmd/noms/noms_root.go
Normal file
112
cmd/noms/noms_root.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2016 Attic Labs, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/attic-labs/noms/cmd/util"
|
||||
"github.com/attic-labs/noms/go/config"
|
||||
"github.com/attic-labs/noms/go/d"
|
||||
"github.com/attic-labs/noms/go/datas"
|
||||
"github.com/attic-labs/noms/go/hash"
|
||||
"github.com/attic-labs/noms/go/types"
|
||||
flag "github.com/juju/gnuflag"
|
||||
)
|
||||
|
||||
var nomsRoot = &util.Command{
|
||||
Run: runRoot,
|
||||
UsageLine: "root <db-spec>",
|
||||
Short: "Get or set the current root hash of the entire database",
|
||||
Long: "See Spelling Objects at https://github.com/attic-labs/noms/blob/master/doc/spelling.md for details on the database argument.",
|
||||
Flags: setupRootFlags,
|
||||
Nargs: 1,
|
||||
}
|
||||
|
||||
var updateRoot = ""
|
||||
|
||||
func setupRootFlags() *flag.FlagSet {
|
||||
flagSet := flag.NewFlagSet("root", flag.ExitOnError)
|
||||
flagSet.StringVar(&updateRoot, "update", "", "Replaces the entire database with the one with the given hash")
|
||||
return flagSet
|
||||
}
|
||||
|
||||
func runRoot(args []string) int {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(os.Stderr, "Not enough arguments")
|
||||
return 0
|
||||
}
|
||||
|
||||
cfg := config.NewResolver()
|
||||
rt, err := cfg.GetRootTracker(args[0])
|
||||
d.CheckErrorNoUsage(err)
|
||||
|
||||
currRoot := rt.Root()
|
||||
|
||||
if updateRoot == "" {
|
||||
fmt.Println(currRoot)
|
||||
return 0
|
||||
}
|
||||
|
||||
if updateRoot[0] == '#' {
|
||||
updateRoot = updateRoot[1:]
|
||||
}
|
||||
h, ok := hash.MaybeParse(updateRoot)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Invalid hash: %s\n", h.String())
|
||||
return 1
|
||||
}
|
||||
|
||||
db, err := cfg.GetDatabase(args[0])
|
||||
d.CheckErrorNoUsage(err)
|
||||
defer db.Close()
|
||||
if !validate(db.ReadValue(h)) {
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Println(`WARNING
|
||||
|
||||
This operation replaces the entire database with the instance having the given
|
||||
hash. The old database becomes eligible for GC.
|
||||
|
||||
ANYTHING NOT SAVED WILL BE LOST
|
||||
|
||||
Continue?
|
||||
`)
|
||||
var input string
|
||||
n, err := fmt.Scanln(&input)
|
||||
d.CheckErrorNoUsage(err)
|
||||
if n != 1 || strings.ToLower(input) != "y" {
|
||||
return 0
|
||||
}
|
||||
|
||||
ok = rt.UpdateRoot(h, currRoot)
|
||||
if !ok {
|
||||
fmt.Fprintln(os.Stderr, "Optimistic concurrency failure")
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Printf("Success. Previous root was: %s\n", currRoot)
|
||||
return 0
|
||||
}
|
||||
|
||||
func validate(r types.Value) bool {
|
||||
rootType := types.MakeMapType(types.StringType, types.MakeRefType(types.ValueType))
|
||||
if !types.IsSubtype(rootType, r.Type()) {
|
||||
fmt.Fprintf(os.Stderr, "Root of database must be %s, but you specified: %s\n", rootType.Describe(), r.Type().Describe())
|
||||
return false
|
||||
}
|
||||
|
||||
return r.(types.Map).Any(func(k, v types.Value) bool {
|
||||
if !datas.IsRefOfCommitType(v.Type()) {
|
||||
fmt.Fprintf(os.Stderr, "Invalid root map. Value for key '%s' is not a ref of commit.", string(k.(types.String)))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
43
cmd/noms/noms_root_test.go
Normal file
43
cmd/noms/noms_root_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2016 Attic Labs, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/attic-labs/noms/go/spec"
|
||||
"github.com/attic-labs/noms/go/types"
|
||||
"github.com/attic-labs/noms/go/util/clienttest"
|
||||
"github.com/attic-labs/testify/suite"
|
||||
)
|
||||
|
||||
func TestNomsRoot(t *testing.T) {
|
||||
suite.Run(t, &nomsRootTestSuite{})
|
||||
}
|
||||
|
||||
type nomsRootTestSuite struct {
|
||||
clienttest.ClientTestSuite
|
||||
}
|
||||
|
||||
func (s *nomsRootTestSuite) TestBasic() {
|
||||
datasetName := "root-get"
|
||||
dsSpec := spec.CreateValueSpecString("ldb", s.LdbDir, datasetName)
|
||||
sp, err := spec.ForDataset(dsSpec)
|
||||
s.NoError(err)
|
||||
defer sp.Close()
|
||||
|
||||
ds := sp.GetDataset()
|
||||
dbSpecStr := spec.CreateDatabaseSpecString("ldb", s.LdbDir)
|
||||
ds, _ = ds.Database().CommitValue(ds, types.String("hello!"))
|
||||
c1, _ := s.MustRun(main, []string{"root", dbSpecStr})
|
||||
s.Equal("gt8mq6r7hvccp98s2vpeu9v9ct4rhloc\n", c1)
|
||||
|
||||
ds, _ = ds.Database().CommitValue(ds, types.String("goodbye"))
|
||||
c2, _ := s.MustRun(main, []string{"root", dbSpecStr})
|
||||
s.Equal("8tj5ctfhbka8fag417huneepg5ji283u\n", c2)
|
||||
|
||||
// TODO: Would be good to test successful --update too, but requires changes to MustRun to allow
|
||||
// input because of prompt :(.
|
||||
}
|
||||
@@ -104,6 +104,19 @@ func (r *Resolver) GetChunkStore(str string) (chunks.ChunkStore, error) {
|
||||
return sp.NewChunkStore(), nil
|
||||
}
|
||||
|
||||
// Resolve string to a RootTracker. Like ResolveDatabase, but returns a RootTracker instead
|
||||
func (r *Resolver) GetRootTracker(str string) (chunks.RootTracker, error) {
|
||||
sp, err := spec.ForDatabase(r.verbose(str, r.ResolveDbSpec(str)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rt chunks.RootTracker = sp.NewChunkStore()
|
||||
if rt == nil {
|
||||
rt = datas.NewHTTPBatchStore(sp.Spec, "")
|
||||
}
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// Resolve string to a dataset. If a config is present,
|
||||
// - if no db prefix is present, assume the default db
|
||||
// - if the db prefix is an alias, replace it
|
||||
|
||||
@@ -94,8 +94,13 @@ type Database interface {
|
||||
// Regardless, Datasets() is updated to match backing storage upon return.
|
||||
FastForward(ds Dataset, newHeadRef types.Ref) (Dataset, error)
|
||||
|
||||
has(h hash.Hash) bool
|
||||
// validatingBatchStore returns the BatchStore used to read and write
|
||||
// groups of values to the database efficiently. This interface is a low-
|
||||
// level detail of the database that should infrequently be needed by
|
||||
// clients.
|
||||
validatingBatchStore() types.BatchStore
|
||||
|
||||
has(h hash.Hash) bool
|
||||
}
|
||||
|
||||
func NewDatabase(cs chunks.ChunkStore) Database {
|
||||
|
||||
@@ -60,7 +60,7 @@ type RemoteDatabaseSuite struct {
|
||||
func (suite *RemoteDatabaseSuite) SetupTest() {
|
||||
suite.cs = chunks.NewTestStore()
|
||||
suite.makeDb = func(cs chunks.ChunkStore) Database {
|
||||
hbs := newHTTPBatchStoreForTest(cs)
|
||||
hbs := NewHTTPBatchStoreForTest(cs)
|
||||
return &RemoteDatabaseClient{newDatabaseCommon(newCachingChunkHaver(hbs), types.NewValueStore(hbs), hbs)}
|
||||
}
|
||||
suite.db = suite.makeDb(suite.cs)
|
||||
|
||||
@@ -53,7 +53,7 @@ type httpBatchStore struct {
|
||||
hints types.Hints
|
||||
}
|
||||
|
||||
func newHTTPBatchStore(baseURL, auth string) *httpBatchStore {
|
||||
func NewHTTPBatchStore(baseURL, auth string) *httpBatchStore {
|
||||
u, err := url.Parse(baseURL)
|
||||
d.PanicIfError(err)
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
|
||||
@@ -49,10 +49,10 @@ func (serv inlineServer) Do(req *http.Request) (resp *http.Response, err error)
|
||||
|
||||
func (suite *HTTPBatchStoreSuite) SetupTest() {
|
||||
suite.cs = chunks.NewTestStore()
|
||||
suite.store = newHTTPBatchStoreForTest(suite.cs)
|
||||
suite.store = NewHTTPBatchStoreForTest(suite.cs)
|
||||
}
|
||||
|
||||
func newHTTPBatchStoreForTest(cs chunks.ChunkStore) *httpBatchStore {
|
||||
func NewHTTPBatchStoreForTest(cs chunks.ChunkStore) *httpBatchStore {
|
||||
serv := inlineServer{httprouter.New()}
|
||||
serv.POST(
|
||||
constants.WriteValuePath,
|
||||
@@ -84,7 +84,7 @@ func newHTTPBatchStoreForTest(cs chunks.ChunkStore) *httpBatchStore {
|
||||
HandleRootGet(w, req, ps, cs)
|
||||
},
|
||||
)
|
||||
hcs := newHTTPBatchStore("http://localhost:9000", "")
|
||||
hcs := NewHTTPBatchStore("http://localhost:9000", "")
|
||||
hcs.httpClient = serv
|
||||
return hcs
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func newAuthenticatingHTTPBatchStoreForTest(suite *HTTPBatchStoreSuite, hostUrl
|
||||
HandleRootPost(w, req, ps, suite.cs)
|
||||
},
|
||||
)
|
||||
hcs := newHTTPBatchStore(hostUrl, "")
|
||||
hcs := NewHTTPBatchStore(hostUrl, "")
|
||||
hcs.httpClient = serv
|
||||
return hcs
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func newBadVersionHTTPBatchStoreForTest(suite *HTTPBatchStoreSuite) *httpBatchSt
|
||||
w.Header().Set(NomsVersionHeader, "BAD")
|
||||
},
|
||||
)
|
||||
hcs := newHTTPBatchStore("http://localhost", "")
|
||||
hcs := NewHTTPBatchStore("http://localhost", "")
|
||||
hcs.httpClient = serv
|
||||
return hcs
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (b *backpressureCS) PutMany(chnx []chunks.Chunk) chunks.BackpressureError {
|
||||
|
||||
func (suite *HTTPBatchStoreSuite) TestPutChunksBackpressure() {
|
||||
bpcs := &backpressureCS{ChunkStore: suite.cs}
|
||||
bs := newHTTPBatchStoreForTest(bpcs)
|
||||
bs := NewHTTPBatchStoreForTest(bpcs)
|
||||
defer bs.Close()
|
||||
defer bpcs.Close()
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func (suite *RemoteToRemoteSuite) SetupTest() {
|
||||
}
|
||||
|
||||
func makeRemoteDb(cs chunks.ChunkStore) Database {
|
||||
hbs := newHTTPBatchStoreForTest(cs)
|
||||
hbs := NewHTTPBatchStoreForTest(cs)
|
||||
return &RemoteDatabaseClient{newDatabaseCommon(newCachingChunkHaver(hbs), types.NewValueStore(hbs), hbs)}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ type RemoteDatabaseClient struct {
|
||||
}
|
||||
|
||||
func NewRemoteDatabase(baseURL, auth string) *RemoteDatabaseClient {
|
||||
httpBS := newHTTPBatchStore(baseURL, auth)
|
||||
httpBS := NewHTTPBatchStore(baseURL, auth)
|
||||
return &RemoteDatabaseClient{newDatabaseCommon(newCachingChunkHaver(httpBS), types.NewValueStore(httpBS), httpBS)}
|
||||
}
|
||||
|
||||
|
||||
@@ -247,6 +247,18 @@ func (m Map) Iter(cb mapIterCallback) {
|
||||
})
|
||||
}
|
||||
|
||||
// Any returns true if cb() return true for any of the items in the map.
|
||||
func (m Map) Any(cb func(k, v Value) bool) (yep bool) {
|
||||
m.Iter(func(k, v Value) bool {
|
||||
if cb(k, v) {
|
||||
yep = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type mapIterAllCallback func(key, value Value)
|
||||
|
||||
func (m Map) IterAll(cb mapIterAllCallback) {
|
||||
|
||||
@@ -808,6 +808,18 @@ func TestMapIter2(t *testing.T) {
|
||||
doTest(getTestRefToValueOrderMap(2, NewTestValueStore()))
|
||||
}
|
||||
|
||||
func TestMapAny(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
p := func(k, v Value) bool {
|
||||
return k.Equals(String("foo")) && v.Equals(String("bar"))
|
||||
}
|
||||
|
||||
assert.False(NewMap().Any(p))
|
||||
assert.False(NewMap(String("foo"), String("baz")).Any(p))
|
||||
assert.True(NewMap(String("foo"), String("bar")).Any(p))
|
||||
}
|
||||
|
||||
func TestMapIterAll(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping test in short mode.")
|
||||
|
||||
Reference in New Issue
Block a user