Merge main

This commit is contained in:
Zach Musgrave
2023-01-17 14:04:53 -08:00
101 changed files with 2177 additions and 2481 deletions
-4
View File
@@ -338,10 +338,6 @@ func SetRemoteUpstreamForBranchRef(dEnv *env.DoltEnv, remote, remoteBranch strin
if err != nil {
return errhand.BuildDError(err.Error()).Build()
}
err = dEnv.RepoState.Save(dEnv.FS)
if err != nil {
return errhand.BuildDError(actions.ErrFailedToSaveRepoState.Error()).AddCause(err).Build()
}
cli.Printf("branch '%s' set up to track '%s/%s'.\n", branchRef.GetPath(), remote, remoteBranch)
return nil
-5
View File
@@ -163,11 +163,6 @@ func clone(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEn
return errhand.VerboseErrorFromError(err)
}
err = clonedEnv.RepoState.Save(clonedEnv.FS)
if err != nil {
return errhand.VerboseErrorFromError(err)
}
return nil
}
+1 -3
View File
@@ -31,7 +31,6 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
"github.com/dolthub/dolt/go/libraries/utils/set"
"github.com/dolthub/dolt/go/store/hash"
)
type AutoResolveStrategy int
@@ -443,8 +442,7 @@ func validateConstraintViolations(ctx context.Context, before, after *doltdb.Roo
return err
}
// todo: this is an expensive way to compute this
_, violators, err := merge.AddForeignKeyViolations(ctx, after, before, set.NewStrSet(tables), hash.Of(nil))
violators, err := merge.GetForeignKeyViolatedTables(ctx, after, before, set.NewStrSet(tables))
if err != nil {
return err
}
+63 -9
View File
@@ -46,6 +46,7 @@ const (
noBatchFlag = "no-batch"
noAutocommitFlag = "no-autocommit"
schemaOnlyFlag = "schema-only"
noCreateDbFlag = "no-create-db"
sqlFileExt = "sql"
csvFileExt = "csv"
@@ -65,7 +66,7 @@ csv,json or parquet file.
`,
Synopsis: []string{
"[-f] [-r {{.LessThan}}result-format{{.GreaterThan}}] [-fn {{.LessThan}}file_name{{.GreaterThan}}] [-d {{.LessThan}}directory{{.GreaterThan}}] [--batch] [--no-batch] [--no-autocommit] ",
"[-f] [-r {{.LessThan}}result-format{{.GreaterThan}}] [-fn {{.LessThan}}file_name{{.GreaterThan}}] [-d {{.LessThan}}directory{{.GreaterThan}}] [--batch] [--no-batch] [--no-autocommit] [--no-create-db] ",
},
}
@@ -97,6 +98,7 @@ func (cmd DumpCmd) ArgParser() *argparser.ArgParser {
ap.SupportsFlag(noBatchFlag, "", "Emit one row per statement, instead of batching multiple rows into each statement.")
ap.SupportsFlag(noAutocommitFlag, "na", "Turn off autocommit for each dumped table. Useful for speeding up loading of output SQL file.")
ap.SupportsFlag(schemaOnlyFlag, "", "Dump a table's schema, without including any data, to the output SQL file.")
ap.SupportsFlag(noCreateDbFlag, "", "Do not write `CREATE DATABASE` statements in SQL files.")
return ap
}
@@ -178,9 +180,20 @@ func (cmd DumpCmd) Exec(ctx context.Context, commandStr string, args []string, d
return HandleVErrAndExitCode(err, usage)
}
err2 := addBulkLoadingParadigms(dEnv, fPath)
if err2 != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err2), usage)
if !apr.Contains(noCreateDbFlag) {
dbName, err := getActiveDatabaseName(ctx, dEnv)
if err != nil {
return HandleVErrAndExitCode(err, usage)
}
err = addCreateDatabaseHeader(dEnv, fPath, dbName)
if err != nil {
return HandleVErrAndExitCode(err, usage)
}
}
err = addBulkLoadingParadigms(dEnv, fPath)
if err != nil {
return HandleVErrAndExitCode(err, usage)
}
for _, tbl := range tblNames {
@@ -460,21 +473,62 @@ func dumpNonSqlTables(ctx context.Context, root *doltdb.RootValue, dEnv *env.Dol
// cc. https://dev.mysql.com/doc/refman/8.0/en/optimizing-innodb-bulk-data-loading.html
// This includes turning off FOREIGN_KEY_CHECKS and UNIQUE_CHECKS off at the beginning of the file.
// Note that the standard mysqldump program turns these variables off.
func addBulkLoadingParadigms(dEnv *env.DoltEnv, fPath string) error {
func addBulkLoadingParadigms(dEnv *env.DoltEnv, fPath string) errhand.VerboseError {
writer, err := dEnv.FS.OpenForWriteAppend(fPath, os.ModePerm)
if err != nil {
return err
return errhand.VerboseErrorFromError(err)
}
_, err = writer.Write([]byte("SET FOREIGN_KEY_CHECKS=0;\n"))
if err != nil {
return err
return errhand.VerboseErrorFromError(err)
}
_, err = writer.Write([]byte("SET UNIQUE_CHECKS=0;\n"))
if err != nil {
return err
return errhand.VerboseErrorFromError(err)
}
return writer.Close()
_ = writer.Close()
return nil
}
// addCreateDatabaseHeader adds a CREATE DATABASE header to prevent `no database selected` errors on dump file ingestion.
func addCreateDatabaseHeader(dEnv *env.DoltEnv, fPath, dbName string) errhand.VerboseError {
writer, err := dEnv.FS.OpenForWriteAppend(fPath, os.ModePerm)
if err != nil {
return errhand.VerboseErrorFromError(err)
}
str := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%[1]s`; USE `%[1]s`; \n", dbName)
_, err = writer.Write([]byte(str))
if err != nil {
return errhand.VerboseErrorFromError(err)
}
_ = writer.Close()
return nil
}
// TODO: find a more elegant way to get database name, possibly implement a method in DoltEnv
// getActiveDatabaseName returns the name of the current active database
func getActiveDatabaseName(ctx context.Context, dEnv *env.DoltEnv) (string, errhand.VerboseError) {
mrEnv, err := env.MultiEnvForDirectory(ctx, dEnv.Config.WriteableConfig(), dEnv.FS, dEnv.Version, dEnv.IgnoreLockFile, dEnv)
if err != nil {
return "", errhand.VerboseErrorFromError(err)
}
// Choose the first DB as the current one. This will be the DB in the working dir if there was one there
var dbName string
err = mrEnv.Iter(func(name string, _ *env.DoltEnv) (stop bool, err error) {
dbName = name
return true, nil
})
if err != nil {
return "", errhand.VerboseErrorFromError(err)
}
return dbName, nil
}
+3 -46
View File
@@ -222,41 +222,6 @@ func pullerProgFunc(ctx context.Context, statsCh chan pull.Stats, language progL
}
}
func progFunc(ctx context.Context, progChan chan pull.PullProgress) {
var latest pull.PullProgress
last := time.Now().UnixNano() - 1
done := false
p := cli.NewEphemeralPrinter()
for !done {
if ctx.Err() != nil {
return
}
select {
case <-ctx.Done():
return
case progress, ok := <-progChan:
if !ok {
done = true
}
latest = progress
case <-time.After(250 * time.Millisecond):
break
}
nowUnix := time.Now().UnixNano()
deltaTime := time.Duration(nowUnix - last)
halfSec := 500 * time.Millisecond
if done || deltaTime > halfSec {
last = nowUnix
if latest.KnownCount > 0 {
p.Printf("Counted chunks: %d, Buffered chunks: %d)\n", latest.KnownCount, latest.DoneCount)
p.Display()
}
}
}
p.Display()
}
// progLanguage is the language to use when displaying progress for a pull from a src db to a sink db.
type progLanguage int
@@ -266,30 +231,22 @@ const (
)
func buildProgStarter(language progLanguage) actions.ProgStarter {
return func(ctx context.Context) (*sync.WaitGroup, chan pull.PullProgress, chan pull.Stats) {
return func(ctx context.Context) (*sync.WaitGroup, chan pull.Stats) {
statsCh := make(chan pull.Stats, 128)
progChan := make(chan pull.PullProgress, 128)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
progFunc(ctx, progChan)
}()
wg.Add(1)
go func() {
defer wg.Done()
pullerProgFunc(ctx, statsCh, language)
}()
return wg, progChan, statsCh
return wg, statsCh
}
}
func stopProgFuncs(cancel context.CancelFunc, wg *sync.WaitGroup, progChan chan pull.PullProgress, statsCh chan pull.Stats) {
func stopProgFuncs(cancel context.CancelFunc, wg *sync.WaitGroup, statsCh chan pull.Stats) {
cancel()
close(progChan)
close(statsCh)
wg.Wait()
}
+3 -3
View File
@@ -189,9 +189,9 @@ func pullTableValue(ctx context.Context, dEnv *env.DoltEnv, srcDB *doltdb.DoltDB
newCtx, cancelFunc := context.WithCancel(ctx)
cli.Println("Retrieving", tblName)
runProgFunc := buildProgStarter(language)
wg, progChan, pullerEventCh := runProgFunc(newCtx)
err = dEnv.DoltDB.PullChunks(ctx, tmpDir, srcDB, []hash.Hash{tblHash}, progChan, pullerEventCh)
stopProgFuncs(cancelFunc, wg, progChan, pullerEventCh)
wg, pullerEventCh := runProgFunc(newCtx)
err = dEnv.DoltDB.PullChunks(ctx, tmpDir, srcDB, []hash.Hash{tblHash}, pullerEventCh)
stopProgFuncs(cancelFunc, wg, pullerEventCh)
if err != nil {
return nil, errhand.BuildDError("Failed reading chunks for remote table '%s' at '%s'", tblName, commitStr).AddCause(err).Build()
}
+1 -1
View File
@@ -56,7 +56,7 @@ import (
)
const (
Version = "0.52.1"
Version = "0.52.4"
)
var dumpDocsCommand = &commands.DumpDocsCmd{}
+2 -2
View File
@@ -15,7 +15,7 @@ require (
github.com/dolthub/fslock v0.0.3
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81
github.com/dolthub/vitess v0.0.0-20230105173952-b40441dfeb0c
github.com/dolthub/vitess v0.0.0-20230111093229-dbe40c6c22d1
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.13.0
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568
@@ -58,7 +58,7 @@ require (
github.com/cenkalti/backoff/v4 v4.1.3
github.com/cespare/xxhash v1.1.0
github.com/creasty/defaults v1.6.0
github.com/dolthub/go-mysql-server v0.14.1-0.20230109233004-891928f34130
github.com/dolthub/go-mysql-server v0.14.1-0.20230117184403-00346c423e7f
github.com/google/flatbuffers v2.0.6+incompatible
github.com/kch42/buzhash v0.0.0-20160816060738-9bdec3dec7c6
github.com/mitchellh/go-ps v1.0.0
+4 -4
View File
@@ -161,16 +161,16 @@ github.com/dolthub/flatbuffers v1.13.0-dh.1 h1:OWJdaPep22N52O/0xsUevxJ6Qfw1M2txC
github.com/dolthub/flatbuffers v1.13.0-dh.1/go.mod h1:CorYGaDmXjHz1Z7i50PYXG1Ricn31GcA2wNOTFIQAKE=
github.com/dolthub/fslock v0.0.3 h1:iLMpUIvJKMKm92+N1fmHVdxJP5NdyDK5bK7z7Ba2s2U=
github.com/dolthub/fslock v0.0.3/go.mod h1:QWql+P17oAAMLnL4HGB5tiovtDuAjdDTPbuqx7bYfa0=
github.com/dolthub/go-mysql-server v0.14.1-0.20230109233004-891928f34130 h1:kgCHirRXDLj+Jye6t6WvdUD9g7qEtEe4eizIRI+fIvU=
github.com/dolthub/go-mysql-server v0.14.1-0.20230109233004-891928f34130/go.mod h1:2ZHPn64+LPJWSfj/GvlaI/6yLSeVnbHTC3ih3ZBhtWg=
github.com/dolthub/go-mysql-server v0.14.1-0.20230117184403-00346c423e7f h1:cOTt7+Y5pEuxOCPX25PvS5fqd+FV18FPOJWDoivPTrY=
github.com/dolthub/go-mysql-server v0.14.1-0.20230117184403-00346c423e7f/go.mod h1:ykkkC0nmCN0Dd7bpm+AeM6w4jcxfV9vIfLQEmajj20I=
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488 h1:0HHu0GWJH0N6a6keStrHhUAK5/o9LVfkh44pvsV4514=
github.com/dolthub/ishell v0.0.0-20221214210346-d7db0b066488/go.mod h1:ehexgi1mPxRTk0Mok/pADALuHbvATulTh6gzr7NzZto=
github.com/dolthub/jsonpath v0.0.0-20210609232853-d49537a30474 h1:xTrR+l5l+1Lfq0NvhiEsctylXinUMFhhsqaEcl414p8=
github.com/dolthub/jsonpath v0.0.0-20210609232853-d49537a30474/go.mod h1:kMz7uXOXq4qRriCEyZ/LUeTqraLJCjf0WVZcUi6TxUY=
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9XGFa6q5Ap4Z/OhNkAMBaK5YeuEzwJt+NZdhiE=
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY=
github.com/dolthub/vitess v0.0.0-20230105173952-b40441dfeb0c h1:/Iws14y/fC75qzgTv2s1KuQCgRGbtC2j1UGPrHLb2xE=
github.com/dolthub/vitess v0.0.0-20230105173952-b40441dfeb0c/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
github.com/dolthub/vitess v0.0.0-20230111093229-dbe40c6c22d1 h1:PNOp1NXSMmvwNibFfMkDpwkck7XA51YH7uKgac2ezGo=
github.com/dolthub/vitess v0.0.0-20230111093229-dbe40c6c22d1/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+12 -4
View File
@@ -18,8 +18,11 @@ import (
"context"
"net/url"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/google/uuid"
"github.com/dolthub/dolt/go/store/blobstore"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/nbs"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/types"
)
@@ -36,11 +39,16 @@ func (fact MemFactory) PrepareDB(ctx context.Context, nbf *types.NomsBinFormat,
// CreateDB creates an in memory backed database
func (fact MemFactory) CreateDB(ctx context.Context, nbf *types.NomsBinFormat, urlObj *url.URL, params map[string]interface{}) (datas.Database, types.ValueReadWriter, tree.NodeStore, error) {
var db datas.Database
storage := &chunks.MemoryStorage{}
cs := storage.NewViewWithFormat(nbf.VersionString())
bs := blobstore.NewInMemoryBlobstore(uuid.New().String())
q := nbs.NewUnlimitedMemQuotaProvider()
cs, err := nbs.NewBSStore(ctx, nbf.VersionString(), bs, defaultMemTableSize, q)
if err != nil {
return nil, nil, nil, err
}
vrw := types.NewValueStore(cs)
ns := tree.NewNodeStore(cs)
db = datas.NewTypesDatabase(vrw, ns)
return db, vrw, ns, nil
}
+1 -1
View File
@@ -60,7 +60,7 @@ func pushDataset(ctx context.Context, destDB, srcDB datas.Database, ds datas.Dat
return err
}
err := pullHash(ctx, destDB, srcDB, []hash.Hash{addr}, tmpDir, nil, nil)
err := pullHash(ctx, destDB, srcDB, []hash.Hash{addr}, tmpDir, nil)
if err != nil {
return err
}
+32 -4
View File
@@ -715,6 +715,36 @@ func (ddb *DoltDB) HasBranch(ctx context.Context, branchName string) (string, bo
return "", false, nil
}
// HasRemoteTrackingBranch returns whether the DB has a remote tracking branch with the name given, case-insensitive.
// Returns the case-sensitive matching branch if found, as well as a bool indicating if there was a case-insensitive match,
// remote tracking branchRef that is the only match for the branchName and any error.
func (ddb *DoltDB) HasRemoteTrackingBranch(ctx context.Context, branchName string) (string, bool, ref.RemoteRef, error) {
remoteRefFound := false
var remoteRef ref.RemoteRef
remoteRefs, err := ddb.GetRemoteRefs(ctx)
if err != nil {
return "", false, ref.RemoteRef{}, err
}
for _, rf := range remoteRefs {
if remRef, ok := rf.(ref.RemoteRef); ok && remRef.GetBranch() == branchName {
if remoteRefFound {
// if there are multiple remotes with matching branch names with defined branch name, it errors
return "", false, ref.RemoteRef{}, fmt.Errorf("'%s' matched multiple remote tracking branches", branchName)
}
remoteRefFound = true
remoteRef = remRef
}
}
if remoteRefFound {
return branchName, true, remoteRef, nil
}
return "", false, ref.RemoteRef{}, nil
}
type RefWithHash struct {
Ref ref.DoltRef
Hash hash.Hash
@@ -1270,10 +1300,9 @@ func (ddb *DoltDB) PullChunks(
tempDir string,
srcDB *DoltDB,
targetHashes []hash.Hash,
progChan chan pull.PullProgress,
statsCh chan pull.Stats,
) error {
return pullHash(ctx, ddb.db, srcDB.db, targetHashes, tempDir, progChan, statsCh)
return pullHash(ctx, ddb.db, srcDB.db, targetHashes, tempDir, statsCh)
}
func pullHash(
@@ -1281,7 +1310,6 @@ func pullHash(
destDB, srcDB datas.Database,
targetHashes []hash.Hash,
tempDir string,
progChan chan pull.PullProgress,
statsCh chan pull.Stats,
) error {
srcCS := datas.ChunkStoreFromDatabase(srcDB)
@@ -1298,7 +1326,7 @@ func pullHash(
return puller.Pull(ctx)
} else {
return pull.Pull(ctx, srcCS, destCS, waf, targetHashes, progChan)
return errors.New("Puller not supported")
}
}
@@ -263,17 +263,12 @@ func mustForkDB(t *testing.T, fromDB *doltdb.DoltDB, bn string, cm *doltdb.Commi
forkEnv := createUninitializedEnv()
err = forkEnv.InitRepo(context.Background(), types.Format_Default, "Bill Billerson", "bill@billerson.com", env.DefaultInitBranch)
require.NoError(t, err)
p1 := make(chan pull.PullProgress)
p2 := make(chan pull.Stats)
ps := make(chan pull.Stats)
go func() {
for range p1 {
for range ps {
}
}()
go func() {
for range p2 {
}
}()
err = forkEnv.DoltDB.PullChunks(context.Background(), "", fromDB, []hash.Hash{h}, p1, p2)
err = forkEnv.DoltDB.PullChunks(context.Background(), "", fromDB, []hash.Hash{h}, ps)
if err == pull.ErrDBUpToDate {
err = nil
}
+3 -27
View File
@@ -37,45 +37,21 @@ func pullerProgFunc(ctx context.Context, statsCh <-chan pull.Stats) {
}
}
func progFunc(ctx context.Context, progChan <-chan pull.PullProgress) {
for {
select {
case <-ctx.Done():
return
default:
}
select {
case <-ctx.Done():
return
case <-progChan:
default:
}
}
}
func NoopRunProgFuncs(ctx context.Context) (*sync.WaitGroup, chan pull.PullProgress, chan pull.Stats) {
func NoopRunProgFuncs(ctx context.Context) (*sync.WaitGroup, chan pull.Stats) {
statsCh := make(chan pull.Stats)
progChan := make(chan pull.PullProgress)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
progFunc(ctx, progChan)
}()
wg.Add(1)
go func() {
defer wg.Done()
pullerProgFunc(ctx, statsCh)
}()
return wg, progChan, statsCh
return wg, statsCh
}
func NoopStopProgFuncs(cancel context.CancelFunc, wg *sync.WaitGroup, progChan chan pull.PullProgress, statsCh chan pull.Stats) {
func NoopStopProgFuncs(cancel context.CancelFunc, wg *sync.WaitGroup, statsCh chan pull.Stats) {
cancel()
close(progChan)
close(statsCh)
wg.Wait()
}
+26 -26
View File
@@ -42,15 +42,15 @@ var ErrFailedToDeleteBackup = errors.New("failed to delete backup")
var ErrFailedToGetBackupDb = errors.New("failed to get backup db")
var ErrUnknownPushErr = errors.New("unknown push error")
type ProgStarter func(ctx context.Context) (*sync.WaitGroup, chan pull.PullProgress, chan pull.Stats)
type ProgStopper func(cancel context.CancelFunc, wg *sync.WaitGroup, progChan chan pull.PullProgress, statsCh chan pull.Stats)
type ProgStarter func(ctx context.Context) (*sync.WaitGroup, chan pull.Stats)
type ProgStopper func(cancel context.CancelFunc, wg *sync.WaitGroup, statsCh chan pull.Stats)
// Push will update a destination branch, in a given destination database if it can be done as a fast forward merge.
// This is accomplished first by verifying that the remote tracking reference for the source database can be updated to
// the given commit via a fast forward merge. If this is the case, an attempt will be made to update the branch in the
// destination db to the given commit via fast forward move. If that succeeds the tracking branch is updated in the
// source db.
func Push(ctx context.Context, tempTableDir string, mode ref.UpdateMode, destRef ref.BranchRef, remoteRef ref.RemoteRef, srcDB, destDB *doltdb.DoltDB, commit *doltdb.Commit, progChan chan pull.PullProgress, statsCh chan pull.Stats) error {
func Push(ctx context.Context, tempTableDir string, mode ref.UpdateMode, destRef ref.BranchRef, remoteRef ref.RemoteRef, srcDB, destDB *doltdb.DoltDB, commit *doltdb.Commit, statsCh chan pull.Stats) error {
var err error
if mode == ref.FastForwardOnly {
canFF, err := srcDB.CanFastForward(ctx, remoteRef, commit)
@@ -67,7 +67,7 @@ func Push(ctx context.Context, tempTableDir string, mode ref.UpdateMode, destRef
return err
}
err = destDB.PullChunks(ctx, tempTableDir, srcDB, []hash.Hash{h}, progChan, statsCh)
err = destDB.PullChunks(ctx, tempTableDir, srcDB, []hash.Hash{h}, statsCh)
if err != nil {
return err
@@ -125,7 +125,7 @@ func DoPush(ctx context.Context, rsr env.RepoStateReader, rsw env.RepoStateWrite
}
// PushTag pushes a commit tag and all underlying data from a local source database to a remote destination database.
func PushTag(ctx context.Context, tempTableDir string, destRef ref.TagRef, srcDB, destDB *doltdb.DoltDB, tag *doltdb.Tag, progChan chan pull.PullProgress, statsCh chan pull.Stats) error {
func PushTag(ctx context.Context, tempTableDir string, destRef ref.TagRef, srcDB, destDB *doltdb.DoltDB, tag *doltdb.Tag, statsCh chan pull.Stats) error {
var err error
addr, err := tag.GetAddr()
@@ -133,7 +133,7 @@ func PushTag(ctx context.Context, tempTableDir string, destRef ref.TagRef, srcDB
return err
}
err = destDB.PullChunks(ctx, tempTableDir, srcDB, []hash.Hash{addr}, progChan, statsCh)
err = destDB.PullChunks(ctx, tempTableDir, srcDB, []hash.Hash{addr}, statsCh)
if err != nil {
return err
@@ -172,9 +172,9 @@ func PushToRemoteBranch(ctx context.Context, rsr env.RepoStateReader, tempTableD
}
newCtx, cancelFunc := context.WithCancel(ctx)
wg, progChan, statsCh := progStarter(newCtx)
err = Push(ctx, tempTableDir, mode, destRef.(ref.BranchRef), remoteRef.(ref.RemoteRef), localDB, remoteDB, cm, progChan, statsCh)
progStopper(cancelFunc, wg, progChan, statsCh)
wg, statsCh := progStarter(newCtx)
err = Push(ctx, tempTableDir, mode, destRef.(ref.BranchRef), remoteRef.(ref.RemoteRef), localDB, remoteDB, cm, statsCh)
progStopper(cancelFunc, wg, statsCh)
switch err {
case nil:
@@ -195,9 +195,9 @@ func pushTagToRemote(ctx context.Context, tempTableDir string, srcRef, destRef r
}
newCtx, cancelFunc := context.WithCancel(ctx)
wg, progChan, statsCh := progStarter(newCtx)
err = PushTag(ctx, tempTableDir, destRef.(ref.TagRef), localDB, remoteDB, tg, progChan, statsCh)
progStopper(cancelFunc, wg, progChan, statsCh)
wg, statsCh := progStarter(newCtx)
err = PushTag(ctx, tempTableDir, destRef.(ref.TagRef), localDB, remoteDB, tg, statsCh)
progStopper(cancelFunc, wg, statsCh)
if err != nil {
return err
@@ -234,23 +234,23 @@ func DeleteRemoteBranch(ctx context.Context, targetRef ref.BranchRef, remoteRef
}
// FetchCommit takes a fetches a commit and all underlying data from a remote source database to the local destination database.
func FetchCommit(ctx context.Context, tempTablesDir string, srcDB, destDB *doltdb.DoltDB, srcDBCommit *doltdb.Commit, progChan chan pull.PullProgress, statsCh chan pull.Stats) error {
func FetchCommit(ctx context.Context, tempTablesDir string, srcDB, destDB *doltdb.DoltDB, srcDBCommit *doltdb.Commit, statsCh chan pull.Stats) error {
h, err := srcDBCommit.HashOf()
if err != nil {
return err
}
return destDB.PullChunks(ctx, tempTablesDir, srcDB, []hash.Hash{h}, progChan, statsCh)
return destDB.PullChunks(ctx, tempTablesDir, srcDB, []hash.Hash{h}, statsCh)
}
// FetchTag takes a fetches a commit tag and all underlying data from a remote source database to the local destination database.
func FetchTag(ctx context.Context, tempTableDir string, srcDB, destDB *doltdb.DoltDB, srcDBTag *doltdb.Tag, progChan chan pull.PullProgress, statsCh chan pull.Stats) error {
func FetchTag(ctx context.Context, tempTableDir string, srcDB, destDB *doltdb.DoltDB, srcDBTag *doltdb.Tag, statsCh chan pull.Stats) error {
addr, err := srcDBTag.GetAddr()
if err != nil {
return err
}
return destDB.PullChunks(ctx, tempTableDir, srcDB, []hash.Hash{addr}, progChan, statsCh)
return destDB.PullChunks(ctx, tempTableDir, srcDB, []hash.Hash{addr}, statsCh)
}
// Clone pulls all data from a remote source database to a local destination database.
@@ -292,9 +292,9 @@ func FetchFollowTags(ctx context.Context, tempTableDir string, srcDB, destDB *do
}
newCtx, cancelFunc := context.WithCancel(ctx)
wg, progChan, statsCh := progStarter(newCtx)
err = FetchTag(ctx, tempTableDir, srcDB, destDB, tag, progChan, statsCh)
progStopper(cancelFunc, wg, progChan, statsCh)
wg, statsCh := progStarter(newCtx)
err = FetchTag(ctx, tempTableDir, srcDB, destDB, tag, statsCh)
progStopper(cancelFunc, wg, statsCh)
if err == nil {
cli.Println()
} else if err == pull.ErrDBUpToDate {
@@ -349,10 +349,10 @@ func FetchRemoteBranch(
// isn't a context leak happening on one path
if progStarter != nil && progStopper != nil {
newCtx, cancelFunc := context.WithCancel(ctx)
wg, progChan, statsCh := progStarter(newCtx)
defer progStopper(cancelFunc, wg, progChan, statsCh)
wg, statsCh := progStarter(newCtx)
defer progStopper(cancelFunc, wg, statsCh)
err = FetchCommit(ctx, tempTablesDir, srcDB, destDB, srcDBCommit, progChan, statsCh)
err = FetchCommit(ctx, tempTablesDir, srcDB, destDB, srcDBCommit, statsCh)
if err == pull.ErrDBUpToDate {
err = nil
@@ -365,7 +365,7 @@ func FetchRemoteBranch(
return srcDBCommit, nil
}
err = FetchCommit(ctx, tempTablesDir, srcDB, destDB, srcDBCommit, nil, nil)
err = FetchCommit(ctx, tempTablesDir, srcDB, destDB, srcDBCommit, nil)
if err == pull.ErrDBUpToDate {
err = nil
@@ -472,15 +472,15 @@ func SyncRoots(ctx context.Context, srcDb, destDb *doltdb.DoltDB, tempTableDir s
}
newCtx, cancelFunc := context.WithCancel(ctx)
wg, progChan, statsCh := progStarter(newCtx)
wg, statsCh := progStarter(newCtx)
defer func() {
progStopper(cancelFunc, wg, progChan, statsCh)
progStopper(cancelFunc, wg, statsCh)
if err == nil {
cli.Println()
}
}()
err = destDb.PullChunks(ctx, tempTableDir, srcDb, []hash.Hash{srcRoot}, progChan, statsCh)
err = destDb.PullChunks(ctx, tempTableDir, srcDb, []hash.Hash{srcRoot}, statsCh)
if err != nil {
return err
}
+5
View File
@@ -976,6 +976,11 @@ func (dEnv *DoltEnv) UpdateBranch(name string, new BranchConfig) error {
}
dEnv.RepoState.Branches[name] = new
err := dEnv.RepoState.Save(dEnv.FS)
if err != nil {
return ErrFailedToWriteRepoState
}
return nil
}
+281 -132
View File
@@ -34,7 +34,9 @@ import (
"github.com/dolthub/dolt/go/libraries/utils/set"
diff2 "github.com/dolthub/dolt/go/store/diff"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/prolly"
"github.com/dolthub/dolt/go/store/types"
"github.com/dolthub/dolt/go/store/val"
)
// constraintViolationsLoadedTable is a collection of items needed to process constraint violations for a single table.
@@ -57,102 +59,294 @@ const (
CvType_CheckConstraint
)
// AddForeignKeyViolations adds foreign key constraint violations to each table.
// todo(andy): pass doltdb.Rootish
func AddForeignKeyViolations(ctx context.Context, newRoot, baseRoot *doltdb.RootValue, tables *set.StrSet, theirRootIsh hash.Hash) (*doltdb.RootValue, *set.StrSet, error) {
type FKViolationReceiver interface {
StartFK(ctx context.Context, fk doltdb.ForeignKey) error
EndCurrFK(ctx context.Context) error
NomsFKViolationFound(ctx context.Context, rowKey, rowValue types.Tuple) error
ProllyFKViolationFound(ctx context.Context, rowKey, rowValue val.Tuple) error
}
// GetForeignKeyViolations returns the violations that have been created as a
// result of the diff between |baseRoot| and |newRoot|. It sends the violations to |receiver|.
func GetForeignKeyViolations(ctx context.Context, newRoot, baseRoot *doltdb.RootValue, tables *set.StrSet, receiver FKViolationReceiver) error {
fkColl, err := newRoot.GetForeignKeyCollection(ctx)
if err != nil {
return nil, nil, err
return err
}
foundViolationsSet := set.NewStrSet(nil)
for _, foreignKey := range fkColl.AllKeys() {
if !foreignKey.IsResolved() || (tables.Size() != 0 && !tables.Contains(foreignKey.TableName)) {
continue
}
err = receiver.StartFK(ctx, foreignKey)
if err != nil {
return err
}
postParent, ok, err := newConstraintViolationsLoadedTable(ctx, foreignKey.ReferencedTableName, foreignKey.ReferencedTableIndex, newRoot)
if err != nil {
return nil, nil, err
return err
}
if !ok {
return nil, nil, fmt.Errorf("foreign key %s should have index %s on table %s but it cannot be found",
return fmt.Errorf("foreign key %s should have index %s on table %s but it cannot be found",
foreignKey.Name, foreignKey.ReferencedTableIndex, foreignKey.ReferencedTableName)
}
postChild, ok, err := newConstraintViolationsLoadedTable(ctx, foreignKey.TableName, foreignKey.TableIndex, newRoot)
if err != nil {
return nil, nil, err
return err
}
if !ok {
return nil, nil, fmt.Errorf("foreign key %s should have index %s on table %s but it cannot be found",
return fmt.Errorf("foreign key %s should have index %s on table %s but it cannot be found",
foreignKey.Name, foreignKey.TableIndex, foreignKey.TableName)
}
jsonData, err := foreignKeyCVJson(foreignKey, postChild.Schema, postParent.Schema)
if err != nil {
return nil, nil, err
}
foundViolations := false
preParent, _, err := newConstraintViolationsLoadedTable(ctx, foreignKey.ReferencedTableName, "", baseRoot)
if err != nil {
if err != doltdb.ErrTableNotFound {
return nil, nil, err
return err
}
// Parent does not exist in the ancestor so we use an empty map
emptyIdx, err := durable.NewEmptyIndex(ctx, postParent.Table.ValueReadWriter(), postParent.Table.NodeStore(), postParent.Schema)
if err != nil {
return nil, nil, err
return err
}
postChild.Table, foundViolations, err = parentFkConstraintViolations(ctx, foreignKey, postParent, postChild, postParent.Schema, emptyIdx, theirRootIsh, jsonData)
err = parentFkConstraintViolations(ctx, foreignKey, postParent, postChild, postParent.Schema, emptyIdx, receiver)
if err != nil {
return nil, nil, err
return err
}
} else {
// Parent exists in the ancestor
postChild.Table, foundViolations, err = parentFkConstraintViolations(ctx, foreignKey, postParent, postChild, preParent.Schema, preParent.RowData, theirRootIsh, jsonData)
err = parentFkConstraintViolations(ctx, foreignKey, postParent, postChild, preParent.Schema, preParent.RowData, receiver)
if err != nil {
return nil, nil, err
return err
}
}
preChild, _, err := newConstraintViolationsLoadedTable(ctx, foreignKey.TableName, "", baseRoot)
if err != nil {
if err != doltdb.ErrTableNotFound {
return nil, nil, err
return err
}
innerFoundViolations := false
// Child does not exist in the ancestor so we use an empty map
emptyIdx, err := durable.NewEmptyIndex(ctx, postChild.Table.ValueReadWriter(), postChild.Table.NodeStore(), postChild.Schema)
if err != nil {
return nil, nil, err
return err
}
postChild.Table, innerFoundViolations, err = childFkConstraintViolations(ctx, foreignKey, postParent, postChild, postChild.Schema, emptyIdx, theirRootIsh, jsonData)
err = childFkConstraintViolations(ctx, foreignKey, postParent, postChild, postChild.Schema, emptyIdx, receiver)
if err != nil {
return nil, nil, err
return err
}
foundViolations = foundViolations || innerFoundViolations
} else {
// Child exists in the ancestor
innerFoundViolations := false
postChild.Table, innerFoundViolations, err = childFkConstraintViolations(ctx, foreignKey, postParent, postChild, preChild.Schema, preChild.RowData, theirRootIsh, jsonData)
err = childFkConstraintViolations(ctx, foreignKey, postParent, postChild, preChild.Schema, preChild.RowData, receiver)
if err != nil {
return nil, nil, err
return err
}
foundViolations = foundViolations || innerFoundViolations
}
newRoot, err = newRoot.PutTable(ctx, postChild.TableName, postChild.Table)
err = receiver.EndCurrFK(ctx)
if err != nil {
return nil, nil, err
}
if foundViolations {
foundViolationsSet.Add(postChild.TableName)
return err
}
}
return newRoot, foundViolationsSet, nil
return nil
}
// AddForeignKeyViolations adds foreign key constraint violations to each table.
// todo(andy): pass doltdb.Rootish
func AddForeignKeyViolations(ctx context.Context, newRoot, baseRoot *doltdb.RootValue, tables *set.StrSet, theirRootIsh hash.Hash) (*doltdb.RootValue, *set.StrSet, error) {
violationWriter := &foreignKeyViolationWriter{rootValue: newRoot, theirRootIsh: theirRootIsh, violatedTables: set.NewStrSet(nil)}
err := GetForeignKeyViolations(ctx, newRoot, baseRoot, tables, violationWriter)
if err != nil {
return nil, nil, err
}
return violationWriter.rootValue, violationWriter.violatedTables, nil
}
// GetForeignKeyViolatedTables returns a list of tables that have foreign key
// violations based on the diff between |newRoot| and |baseRoot|.
func GetForeignKeyViolatedTables(ctx context.Context, newRoot, baseRoot *doltdb.RootValue, tables *set.StrSet) (*set.StrSet, error) {
handler := &foreignKeyViolationTracker{tableSet: set.NewStrSet(nil)}
err := GetForeignKeyViolations(ctx, newRoot, baseRoot, tables, handler)
if err != nil {
return nil, err
}
return handler.tableSet, nil
}
// foreignKeyViolationTracker tracks which tables have foreign key violations
type foreignKeyViolationTracker struct {
tableSet *set.StrSet
currFk doltdb.ForeignKey
}
func (f *foreignKeyViolationTracker) StartFK(ctx context.Context, fk doltdb.ForeignKey) error {
f.currFk = fk
return nil
}
func (f *foreignKeyViolationTracker) EndCurrFK(ctx context.Context) error {
return nil
}
func (f *foreignKeyViolationTracker) NomsFKViolationFound(ctx context.Context, rowKey, rowValue types.Tuple) error {
f.tableSet.Add(f.currFk.TableName)
return nil
}
func (f *foreignKeyViolationTracker) ProllyFKViolationFound(ctx context.Context, rowKey, rowValue val.Tuple) error {
f.tableSet.Add(f.currFk.TableName)
return nil
}
var _ FKViolationReceiver = (*foreignKeyViolationTracker)(nil)
// foreignKeyViolationWriter updates rootValue with the foreign key constraint violations.
type foreignKeyViolationWriter struct {
rootValue *doltdb.RootValue
theirRootIsh hash.Hash
violatedTables *set.StrSet
currFk doltdb.ForeignKey
currTbl *doltdb.Table
// prolly
artEditor prolly.ArtifactsEditor
kd val.TupleDesc
cInfoJsonData []byte
// noms
violMapEditor *types.MapEditor
nomsVInfo types.JSON
}
var _ FKViolationReceiver = (*foreignKeyViolationWriter)(nil)
func (f *foreignKeyViolationWriter) StartFK(ctx context.Context, fk doltdb.ForeignKey) error {
f.currFk = fk
tbl, ok, err := f.rootValue.GetTable(ctx, fk.TableName)
if err != nil {
return err
}
if !ok {
return doltdb.ErrTableNotFound
}
f.currTbl = tbl
refTbl, ok, err := f.rootValue.GetTable(ctx, fk.ReferencedTableName)
if err != nil {
return err
}
if !ok {
return doltdb.ErrTableNotFound
}
sch, err := tbl.GetSchema(ctx)
if err != nil {
return err
}
refSch, err := refTbl.GetSchema(ctx)
if err != nil {
return err
}
jsonData, err := foreignKeyCVJson(fk, sch, refSch)
if err != nil {
return err
}
if types.IsFormat_DOLT(tbl.Format()) {
arts, err := tbl.GetArtifacts(ctx)
if err != nil {
return err
}
artMap := durable.ProllyMapFromArtifactIndex(arts)
f.artEditor = artMap.Editor()
f.cInfoJsonData = jsonData
f.kd = sch.GetKeyDescriptor()
} else {
violMap, err := tbl.GetConstraintViolations(ctx)
if err != nil {
return err
}
f.violMapEditor = violMap.Edit()
f.nomsVInfo, err = jsonDataToNomsValue(ctx, tbl.ValueReadWriter(), jsonData)
if err != nil {
return err
}
}
return nil
}
func (f *foreignKeyViolationWriter) EndCurrFK(ctx context.Context) error {
if types.IsFormat_DOLT(f.currTbl.Format()) {
artMap, err := f.artEditor.Flush(ctx)
if err != nil {
return err
}
artIdx := durable.ArtifactIndexFromProllyMap(artMap)
tbl, err := f.currTbl.SetArtifacts(ctx, artIdx)
if err != nil {
return err
}
f.rootValue, err = f.rootValue.PutTable(ctx, f.currFk.TableName, tbl)
if err != nil {
return err
}
return nil
}
violMap, err := f.violMapEditor.Map(ctx)
if err != nil {
return err
}
tbl, err := f.currTbl.SetConstraintViolations(ctx, violMap)
if err != nil {
return err
}
f.rootValue, err = f.rootValue.PutTable(ctx, f.currFk.TableName, tbl)
if err != nil {
return err
}
return nil
}
func (f *foreignKeyViolationWriter) NomsFKViolationFound(ctx context.Context, rowKey, rowValue types.Tuple) error {
cvKey, cvVal, err := toConstraintViolationRow(ctx, CvType_ForeignKey, f.nomsVInfo, rowKey, rowValue)
if err != nil {
return err
}
f.violMapEditor.Set(cvKey, cvVal)
f.violatedTables.Add(f.currFk.TableName)
return nil
}
func (f *foreignKeyViolationWriter) ProllyFKViolationFound(ctx context.Context, rowKey, rowValue val.Tuple) error {
meta := prolly.ConstraintViolationMeta{VInfo: f.cInfoJsonData, Value: rowValue}
err := f.artEditor.ReplaceConstraintViolation(ctx, rowKey, f.theirRootIsh, prolly.ArtifactTypeForeignKeyViol, meta)
if err != nil {
return handleFkMultipleViolForRowErr(err, f.kd, f.currFk.TableName)
}
f.violatedTables.Add(f.currFk.TableName)
return nil
}
var _ FKViolationReceiver = (*foreignKeyViolationWriter)(nil)
// parentFkConstraintViolations processes foreign key constraint violations for the parent in a foreign key.
func parentFkConstraintViolations(
ctx context.Context,
@@ -160,15 +354,14 @@ func parentFkConstraintViolations(
postParent, postChild *constraintViolationsLoadedTable,
preParentSch schema.Schema,
preParentRowData durable.Index,
theirRootIsh hash.Hash,
jsonData []byte,
) (*doltdb.Table, bool, error) {
receiver FKViolationReceiver,
) error {
if preParentRowData.Format() == types.Format_DOLT {
m := durable.ProllyMapFromIndex(preParentRowData)
return prollyParentFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, theirRootIsh, jsonData)
return prollyParentFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, receiver)
}
m := durable.NomsMapFromIndex(preParentRowData)
return nomsParentFkConstraintViolations(ctx, foreignKey, postParent, postChild, preParentSch, m, jsonData)
return nomsParentFkConstraintViolations(ctx, foreignKey, postParent, postChild, preParentSch, m, receiver)
}
// childFkConstraintViolations handles processing the reference options on a child, or creating a violation if
@@ -179,15 +372,14 @@ func childFkConstraintViolations(
postParent, postChild *constraintViolationsLoadedTable,
preChildSch schema.Schema,
preChildRowData durable.Index,
ourCmHash hash.Hash,
jsonData []byte) (*doltdb.Table, bool, error) {
receiver FKViolationReceiver) error {
if preChildRowData.Format() == types.Format_DOLT {
m := durable.ProllyMapFromIndex(preChildRowData)
return prollyChildFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, ourCmHash, jsonData)
return prollyChildFkConstraintViolations(ctx, foreignKey, postParent, postChild, m, receiver)
}
m := durable.NomsMapFromIndex(preChildRowData)
return nomsChildFkConstraintViolations(ctx, foreignKey, postParent, postChild, preChildSch, m)
return nomsChildFkConstraintViolations(ctx, foreignKey, postParent, postChild, preChildSch, m, receiver)
}
func nomsParentFkConstraintViolations(
@@ -196,21 +388,10 @@ func nomsParentFkConstraintViolations(
postParent, postChild *constraintViolationsLoadedTable,
preParentSch schema.Schema,
preParentRowData types.Map,
jsonData []byte) (*doltdb.Table, bool, error) {
receiver FKViolationReceiver) error {
foundViolations := false
postParentIndexTags := postParent.Index.IndexedColumnTags()
postChildIndexTags := postChild.Index.IndexedColumnTags()
postChildCVMap, err := postChild.Table.GetConstraintViolations(ctx)
if err != nil {
return nil, false, err
}
postChildCVMapEditor := postChildCVMap.Edit()
vInfo, err := jsonDataToNomsValue(ctx, postParent.Table.ValueReadWriter(), jsonData)
if err != nil {
return nil, false, err
}
differ := diff.NewRowDiffer(ctx, preParentRowData.Format(), preParentSch, postParent.Schema, 1024)
defer differ.Close()
@@ -218,11 +399,11 @@ func nomsParentFkConstraintViolations(
for {
diffSlice, hasMore, err := differ.GetDiffs(1, 10*time.Second)
if err != nil {
return nil, false, err
return err
}
if len(diffSlice) != 1 {
if hasMore {
return nil, false, fmt.Errorf("no diff returned but should have errored earlier")
return fmt.Errorf("no diff returned but should have errored earlier")
}
break
}
@@ -231,7 +412,7 @@ func nomsParentFkConstraintViolations(
case types.DiffChangeRemoved, types.DiffChangeModified:
postParentRow, err := row.FromNoms(postParent.Schema, rowDiff.KeyValue.(types.Tuple), rowDiff.OldValue.(types.Tuple))
if err != nil {
return nil, false, err
return err
}
hasNulls := false
for _, tag := range postParentIndexTags {
@@ -246,7 +427,7 @@ func nomsParentFkConstraintViolations(
postParentIndexPartialKey, err := row.ReduceToIndexPartialKey(foreignKey.TableColumns, postParent.Index, postParentRow)
if err != nil {
return nil, false, err
return err
}
shouldContinue, err := func() (bool, error) {
@@ -264,7 +445,7 @@ func nomsParentFkConstraintViolations(
return false, nil
}()
if err != nil {
return nil, false, err
return err
}
if shouldContinue {
continue
@@ -272,36 +453,30 @@ func nomsParentFkConstraintViolations(
postParentIndexPartialKeySlice, err := postParentIndexPartialKey.AsSlice()
if err != nil {
return nil, false, err
return err
}
for i := 0; i < len(postChildIndexTags); i++ {
postParentIndexPartialKeySlice[2*i] = types.Uint(postChildIndexTags[i])
}
postChildIndexPartialKey, err := types.NewTuple(postChild.Table.Format(), postParentIndexPartialKeySlice...)
if err != nil {
return nil, false, err
return err
}
changeViolates, err := nomsParentFkConstraintViolationsProcess(ctx, foreignKey, postChild, postChildIndexPartialKey, postChildCVMapEditor, vInfo)
err = nomsParentFkConstraintViolationsProcess(ctx, foreignKey, postChild, postChildIndexPartialKey, receiver)
if err != nil {
return nil, false, err
return err
}
foundViolations = foundViolations || changeViolates
case types.DiffChangeAdded:
// We don't do anything if a parent row was added
default:
return nil, false, fmt.Errorf("unknown diff change type")
return fmt.Errorf("unknown diff change type")
}
if !hasMore {
break
}
}
postChildCVMap, err = postChildCVMapEditor.Map(ctx)
if err != nil {
return nil, false, err
}
updatedTbl, err := postChild.Table.SetConstraintViolations(ctx, postChildCVMap)
return updatedTbl, foundViolations, err
return nil
}
func nomsParentFkConstraintViolationsProcess(
@@ -309,13 +484,11 @@ func nomsParentFkConstraintViolationsProcess(
foreignKey doltdb.ForeignKey,
postChild *constraintViolationsLoadedTable,
postChildIndexPartialKey types.Tuple,
postChildCVMapEditor *types.MapEditor,
vInfo types.JSON,
) (bool, error) {
receiver FKViolationReceiver,
) error {
indexData := durable.NomsMapFromIndex(postChild.IndexData)
rowData := durable.NomsMapFromIndex(postChild.RowData)
foundViolation := false
mapIter := noms.NewNomsRangeReader(
postChild.IndexSchema,
indexData,
@@ -326,31 +499,29 @@ func nomsParentFkConstraintViolationsProcess(
for postChildIndexRow, err = mapIter.ReadRow(ctx); err == nil; postChildIndexRow, err = mapIter.ReadRow(ctx) {
postChildIndexKey, err := postChildIndexRow.NomsMapKey(postChild.IndexSchema).Value(ctx)
if err != nil {
return false, err
return err
}
postChildRowKey, err := postChild.Index.ToTableTuple(ctx, postChildIndexKey.(types.Tuple), postChild.Table.Format())
if err != nil {
return false, err
return err
}
postChildRowVal, ok, err := rowData.MaybeGetTuple(ctx, postChildRowKey)
if err != nil {
return false, err
return err
}
if !ok {
return false, fmt.Errorf("index %s on %s contains data that table does not", foreignKey.TableIndex, foreignKey.TableName)
return fmt.Errorf("index %s on %s contains data that table does not", foreignKey.TableIndex, foreignKey.TableName)
}
cvKey, cvVal, err := toConstraintViolationRow(ctx, CvType_ForeignKey, vInfo, postChildRowKey, postChildRowVal)
err = receiver.NomsFKViolationFound(ctx, postChildRowKey, postChildRowVal)
if err != nil {
return false, err
return err
}
postChildCVMapEditor.Set(cvKey, cvVal)
foundViolation = true
}
if err != io.EOF {
return false, err
return err
}
return foundViolation, nil
return nil
}
// nomsChildFkConstraintViolations processes foreign key constraint violations for the child in a foreign key.
@@ -360,8 +531,8 @@ func nomsChildFkConstraintViolations(
postParent, postChild *constraintViolationsLoadedTable,
preChildSch schema.Schema,
preChildRowData types.Map,
) (*doltdb.Table, bool, error) {
foundViolations := false
receiver FKViolationReceiver,
) error {
var postParentIndexTags, postChildIndexTags []uint64
if postParent.Index.Name() == "" {
postParentIndexTags = foreignKey.ReferencedTableColumns
@@ -370,20 +541,6 @@ func nomsChildFkConstraintViolations(
postParentIndexTags = postParent.Index.IndexedColumnTags()
postChildIndexTags = postChild.Index.IndexedColumnTags()
}
postChildCVMap, err := postChild.Table.GetConstraintViolations(ctx)
if err != nil {
return nil, false, err
}
postChildCVMapEditor := postChildCVMap.Edit()
jsonData, err := foreignKeyCVJson(foreignKey, postChild.Schema, postParent.Schema)
if err != nil {
return nil, false, err
}
vInfo, err := jsonDataToNomsValue(ctx, postChild.Table.ValueReadWriter(), jsonData)
if err != nil {
return nil, false, err
}
differ := diff.NewRowDiffer(ctx, preChildRowData.Format(), preChildSch, postChild.Schema, 1024)
defer differ.Close()
@@ -391,11 +548,11 @@ func nomsChildFkConstraintViolations(
for {
diffSlice, hasMore, err := differ.GetDiffs(1, 10*time.Second)
if err != nil {
return nil, false, err
return err
}
if len(diffSlice) != 1 {
if hasMore {
return nil, false, fmt.Errorf("no diff returned but should have errored earlier")
return fmt.Errorf("no diff returned but should have errored earlier")
}
break
}
@@ -404,7 +561,7 @@ func nomsChildFkConstraintViolations(
case types.DiffChangeAdded, types.DiffChangeModified:
postChildRow, err := row.FromNoms(postChild.Schema, rowDiff.KeyValue.(types.Tuple), rowDiff.NewValue.(types.Tuple))
if err != nil {
return nil, false, err
return err
}
hasNulls := false
for _, tag := range postChildIndexTags {
@@ -419,51 +576,44 @@ func nomsChildFkConstraintViolations(
postChildIndexPartialKey, err := row.ReduceToIndexPartialKey(postChildIndexTags, postChild.Index, postChildRow)
if err != nil {
return nil, false, err
return err
}
postChildIndexPartialKeySlice, err := postChildIndexPartialKey.AsSlice()
if err != nil {
return nil, false, err
return err
}
for i := 0; i < len(postParentIndexTags); i++ {
postChildIndexPartialKeySlice[2*i] = types.Uint(postParentIndexTags[i])
}
parentPartialKey, err := types.NewTuple(postChild.Table.Format(), postChildIndexPartialKeySlice...)
if err != nil {
return nil, false, err
return err
}
diffViolates, err := childFkConstraintViolationsProcess(ctx, foreignKey, postParent, postChild, rowDiff, parentPartialKey, postChildCVMapEditor, vInfo)
err = childFkConstraintViolationsProcess(ctx, postParent, rowDiff, parentPartialKey, receiver)
if err != nil {
return nil, false, err
return err
}
foundViolations = foundViolations || diffViolates
case types.DiffChangeRemoved:
// We don't do anything if a child row was removed
default:
return nil, false, fmt.Errorf("unknown diff change type")
return fmt.Errorf("unknown diff change type")
}
if !hasMore {
break
}
}
postChildCVMap, err = postChildCVMapEditor.Map(ctx)
if err != nil {
return nil, false, err
}
updatedTbl, err := postChild.Table.SetConstraintViolations(ctx, postChildCVMap)
return updatedTbl, foundViolations, err
return nil
}
// childFkConstraintViolationsProcess handles processing the constraint violations for the child of a foreign key.
func childFkConstraintViolationsProcess(
ctx context.Context,
foreignKey doltdb.ForeignKey,
postParent, postChild *constraintViolationsLoadedTable,
postParent *constraintViolationsLoadedTable,
rowDiff *diff2.Difference,
parentPartialKey types.Tuple,
postChildCVMapEditor *types.MapEditor,
vInfo types.JSON,
) (bool, error) {
receiver FKViolationReceiver,
) error {
var mapIter table.ReadCloser = noms.NewNomsRangeReader(
postParent.IndexSchema,
durable.NomsMapFromIndex(postParent.IndexData),
@@ -472,16 +622,15 @@ func childFkConstraintViolationsProcess(
// If the row exists in the parent, then we don't need to do anything
if _, err := mapIter.ReadRow(ctx); err != nil {
if err != io.EOF {
return false, err
return err
}
cvKey, cvVal, err := toConstraintViolationRow(ctx, CvType_ForeignKey, vInfo, rowDiff.KeyValue.(types.Tuple), rowDiff.NewValue.(types.Tuple))
err = receiver.NomsFKViolationFound(ctx, rowDiff.KeyValue.(types.Tuple), rowDiff.NewValue.(types.Tuple))
if err != nil {
return false, err
return err
}
postChildCVMapEditor.Set(cvKey, cvVal)
return true, nil
return nil
}
return false, nil
return nil
}
// newConstraintViolationsLoadedTable returns a *constraintViolationsLoadedTable. Returns false if the table was loaded
@@ -28,7 +28,6 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/table/editor/creation"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/pool"
"github.com/dolthub/dolt/go/store/prolly"
"github.com/dolthub/dolt/go/store/prolly/tree"
@@ -40,8 +39,7 @@ func prollyParentFkConstraintViolations(
foreignKey doltdb.ForeignKey,
postParent, postChild *constraintViolationsLoadedTable,
preParentRowData prolly.Map,
theirRootIsh hash.Hash,
jsonData []byte) (*doltdb.Table, bool, error) {
receiver FKViolationReceiver) error {
postParentRowData := durable.ProllyMapFromIndex(postParent.RowData)
postParentIndexData := durable.ProllyMapFromIndex(postParent.IndexData)
@@ -49,20 +47,11 @@ func prollyParentFkConstraintViolations(
partialDesc := idxDesc.PrefixDesc(len(foreignKey.TableColumns))
partialKB := val.NewTupleBuilder(partialDesc)
artIdx, err := postChild.Table.GetArtifacts(ctx)
if err != nil {
return nil, false, err
}
artM := durable.ProllyMapFromArtifactIndex(artIdx)
artEditor := artM.Editor()
childPriIdx := durable.ProllyMapFromIndex(postChild.RowData)
childScndryIdx := durable.ProllyMapFromIndex(postChild.IndexData)
primaryKD, _ := childPriIdx.Descriptors()
var foundViolation bool
err = prolly.DiffMaps(ctx, preParentRowData, postParentRowData, func(ctx context.Context, diff tree.Diff) error {
err := prolly.DiffMaps(ctx, preParentRowData, postParentRowData, func(ctx context.Context, diff tree.Diff) error {
switch diff.Type {
case tree.RemovedDiff, tree.ModifiedDiff:
partialKey, hadNulls := makePartialKey(partialKB, foreignKey.ReferencedTableColumns, postParent.Index, postParent.Schema, val.Tuple(diff.Key), val.Tuple(diff.From), preParentRowData.Pool())
@@ -88,13 +77,11 @@ func prollyParentFkConstraintViolations(
// All equivalent parents were deleted, let's check for dangling children.
// We search for matching keys in the child's secondary index
found, err := createCVsForPartialKeyMatches(ctx, partialKey, partialDesc, artEditor, primaryKD, childPriIdx, childScndryIdx, childPriIdx.Pool(), jsonData, theirRootIsh, postChild.TableName)
err = createCVsForPartialKeyMatches(ctx, partialKey, partialDesc, primaryKD, childPriIdx, childScndryIdx, childPriIdx.Pool(), receiver)
if err != nil {
return err
}
foundViolation = foundViolation || found
case tree.AddedDiff:
default:
panic("unhandled diff type")
@@ -103,20 +90,10 @@ func prollyParentFkConstraintViolations(
return nil
})
if err != nil && err != io.EOF {
return nil, false, err
return err
}
artM, err = artEditor.Flush(ctx)
if err != nil {
return nil, false, err
}
updated, err := postChild.Table.SetArtifacts(ctx, durable.ArtifactIndexFromProllyMap(artM))
if err != nil {
return nil, false, err
}
return updated, foundViolation, nil
return nil
}
func prollyChildFkConstraintViolations(
@@ -124,27 +101,16 @@ func prollyChildFkConstraintViolations(
foreignKey doltdb.ForeignKey,
postParent, postChild *constraintViolationsLoadedTable,
preChildRowData prolly.Map,
theirRootIsh hash.Hash,
jsonData []byte) (*doltdb.Table, bool, error) {
receiver FKViolationReceiver) error {
postChildRowData := durable.ProllyMapFromIndex(postChild.RowData)
idxDesc := postChild.Index.Schema().GetKeyDescriptor()
partialDesc := idxDesc.PrefixDesc(len(foreignKey.TableColumns))
partialKB := val.NewTupleBuilder(partialDesc)
artIdx, err := postChild.Table.GetArtifacts(ctx)
if err != nil {
return nil, false, err
}
artM := durable.ProllyMapFromArtifactIndex(artIdx)
artEditor := artM.Editor()
parentScndryIdx := durable.ProllyMapFromIndex(postParent.IndexData)
var foundViolation bool
kd, vd := postChildRowData.Descriptors()
err = prolly.DiffMaps(ctx, preChildRowData, postChildRowData, func(ctx context.Context, diff tree.Diff) error {
err := prolly.DiffMaps(ctx, preChildRowData, postChildRowData, func(ctx context.Context, diff tree.Diff) error {
switch diff.Type {
case tree.AddedDiff, tree.ModifiedDiff:
k, v := val.Tuple(diff.Key), val.Tuple(diff.To)
@@ -153,11 +119,10 @@ func prollyChildFkConstraintViolations(
return nil
}
found, err := createCVIfNoPartialKeyMatches(ctx, k, v, partialKey, kd, vd, partialDesc, parentScndryIdx, artEditor, jsonData, theirRootIsh, postChild.TableName)
err := createCVIfNoPartialKeyMatches(ctx, k, v, partialKey, partialDesc, parentScndryIdx, receiver)
if err != nil {
return err
}
foundViolation = foundViolation || found
case tree.RemovedDiff:
default:
panic("unhandled diff type")
@@ -165,51 +130,36 @@ func prollyChildFkConstraintViolations(
return nil
})
if err != nil && err != io.EOF {
return nil, false, err
return err
}
artM, err = artEditor.Flush(ctx)
if err != nil {
return nil, false, err
}
updated, err := postChild.Table.SetArtifacts(ctx, durable.ArtifactIndexFromProllyMap(artM))
if err != nil {
return nil, false, err
}
return updated, foundViolation, nil
return nil
}
func createCVIfNoPartialKeyMatches(
ctx context.Context,
k, v, partialKey val.Tuple,
kd, vd, partialKeyDesc val.TupleDesc,
partialKeyDesc val.TupleDesc,
idx prolly.Map,
editor prolly.ArtifactsEditor,
jsonData []byte,
theirRootIsh hash.Hash,
tblName string) (bool, error) {
receiver FKViolationReceiver) error {
itr, err := creation.NewPrefixItr(ctx, partialKey, partialKeyDesc, idx)
if err != nil {
return false, err
return err
}
_, _, err = itr.Next(ctx)
if err != nil && err != io.EOF {
return false, err
return err
}
if err == nil {
return false, nil
return nil
}
meta := prolly.ConstraintViolationMeta{VInfo: jsonData, Value: v}
err = editor.ReplaceConstraintViolation(ctx, k, theirRootIsh, prolly.ArtifactTypeForeignKeyViol, meta)
err = receiver.ProllyFKViolationFound(ctx, k, v)
if err != nil {
return false, handleFkMultipleViolForRowErr(err, kd, tblName)
return err
}
return true, nil
return nil
}
func handleFkMultipleViolForRowErr(err error, kd val.TupleDesc, tblName string) error {
@@ -238,26 +188,21 @@ func createCVsForPartialKeyMatches(
ctx context.Context,
partialKey val.Tuple,
partialKeyDesc val.TupleDesc,
editor prolly.ArtifactsEditor,
primaryKD val.TupleDesc,
primaryIdx prolly.Map,
secondaryIdx prolly.Map,
pool pool.BuffPool,
jsonData []byte,
theirRootIsh hash.Hash,
tblName string,
) (bool, error) {
createdViolation := false
receiver FKViolationReceiver,
) error {
itr, err := creation.NewPrefixItr(ctx, partialKey, partialKeyDesc, secondaryIdx)
if err != nil {
return false, err
return err
}
kb := val.NewTupleBuilder(primaryKD)
for k, _, err := itr.Next(ctx); err == nil; k, _, err = itr.Next(ctx) {
createdViolation = true
// convert secondary idx entry to primary row key
// the pks of the table are the last keys of the index
@@ -274,20 +219,19 @@ func createCVsForPartialKeyMatches(
return nil
})
if err != nil {
return false, err
return err
}
meta := prolly.ConstraintViolationMeta{VInfo: jsonData, Value: value}
err = editor.ReplaceConstraintViolation(ctx, primaryIdxKey, theirRootIsh, prolly.ArtifactTypeForeignKeyViol, meta)
err = receiver.ProllyFKViolationFound(ctx, primaryIdxKey, value)
if err != nil {
return false, handleFkMultipleViolForRowErr(err, primaryKD, tblName)
return err
}
}
if err != nil && err != io.EOF {
return false, err
return err
}
return createdViolation, nil
return nil
}
func makePartialKey(kb *val.TupleBuilder, tags []uint64, idxSch schema.Index, tblSch schema.Schema, k, v val.Tuple, pool pool.BuffPool) (val.Tuple, bool) {
+137 -130
View File
@@ -41,6 +41,8 @@ import (
var ErrUnimplemented = errors.New("unimplemented")
const RepoPathField = "repo_path"
type RemoteChunkStore struct {
HttpHost string
httpScheme string
@@ -84,25 +86,20 @@ func getRepoPath(req repoRequest) string {
func (rs *RemoteChunkStore) HasChunks(ctx context.Context, req *remotesapi.HasChunksRequest) (*remotesapi.HasChunksResponse, error) {
logger := getReqLogger(rs.lgr, "HasChunks")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
cs, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found repo %s", repoPath)
hashes, hashToIndex := remotestorage.ParseByteSlices(req.Hashes)
absent, err := cs.HasMany(ctx, hashes)
if err != nil {
logger.WithError(err).Error("error calling HasMany")
return nil, status.Error(codes.Internal, "HasMany failure:"+err.Error())
}
@@ -114,12 +111,15 @@ func (rs *RemoteChunkStore) HasChunks(ctx context.Context, req *remotesapi.HasCh
n++
}
//logger(fmt.Sprintf("missing chunks: %v", indices))
resp := &remotesapi.HasChunksResponse{
Absent: indices,
}
logger = logger.WithFields(logrus.Fields{
"num_requested": len(hashToIndex),
"num_absent": len(indices),
})
return resp, nil
}
@@ -141,65 +141,89 @@ func (rs *RemoteChunkStore) getRelativeStorePath(cs RemoteSrvStore) (string, err
func (rs *RemoteChunkStore) GetDownloadLocations(ctx context.Context, req *remotesapi.GetDownloadLocsRequest) (*remotesapi.GetDownloadLocsResponse, error) {
logger := getReqLogger(rs.lgr, "GetDownloadLocations")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
cs, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found repo %s", repoPath)
hashes, _ := remotestorage.ParseByteSlices(req.ChunkHashes)
prefix, err := rs.getRelativeStorePath(cs)
if err != nil {
logger.WithError(err).Error("error getting file store path for chunk store")
return nil, err
}
numHashes := len(hashes)
locations, err := cs.GetChunkLocationsWithPaths(hashes)
if err != nil {
logger.WithError(err).Error("error getting chunk locations for hashes")
return nil, err
}
md, _ := metadata.FromIncomingContext(ctx)
var locs []*remotesapi.DownloadLoc
numRanges := 0
for loc, hashToRange := range locations {
if len(hashToRange) == 0 {
continue
}
numRanges += len(hashToRange)
var ranges []*remotesapi.RangeChunk
for h, r := range hashToRange {
hCpy := h
ranges = append(ranges, &remotesapi.RangeChunk{Hash: hCpy[:], Offset: r.Offset, Length: r.Length})
}
url, err := rs.getDownloadUrl(logger, md, prefix+"/"+loc)
if err != nil {
logger.Println("Failed to sign request", err)
return nil, err
}
url := rs.getDownloadUrl(md, prefix+"/"+loc)
preurl := url.String()
url, err = rs.sealer.Seal(url)
if err != nil {
logger.Println("Failed to seal request", err)
logger.WithError(err).Error("error sealing download url")
return nil, err
}
logger.Println("The URL is", preurl, "the ranges are", ranges, "sealed url", url.String())
logger.WithFields(logrus.Fields{
"url": preurl,
"ranges": ranges,
"sealed_url": url.String(),
}).Trace("generated sealed url")
getRange := &remotesapi.HttpGetRange{Url: url.String(), Ranges: ranges}
locs = append(locs, &remotesapi.DownloadLoc{Location: &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: getRange}})
}
logger = logger.WithFields(logrus.Fields{
"num_requested": numHashes,
"num_urls": len(locations),
"num_ranges": numRanges,
})
return &remotesapi.GetDownloadLocsResponse{Locs: locs}, nil
}
func (rs *RemoteChunkStore) StreamDownloadLocations(stream remotesapi.ChunkStoreService_StreamDownloadLocationsServer) error {
logger := getReqLogger(rs.lgr, "StreamDownloadLocations")
defer func() { logger.Println("finished") }()
ologger := getReqLogger(rs.lgr, "StreamDownloadLocations")
numMessages := 0
numHashes := 0
numUrls := 0
numRanges := 0
defer func() {
ologger.WithFields(logrus.Fields{
"num_messages": numMessages,
"num_requested": numHashes,
"num_urls": numUrls,
"num_ranges": numRanges,
}).Info("finished")
}()
logger := ologger
md, _ := metadata.FromIncomingContext(stream.Context())
@@ -215,50 +239,58 @@ func (rs *RemoteChunkStore) StreamDownloadLocations(stream remotesapi.ChunkStore
return err
}
numMessages += 1
nextPath := getRepoPath(req)
if nextPath != repoPath {
repoPath = nextPath
logger = ologger.WithField(RepoPathField, repoPath)
cs, err = rs.getStore(logger, repoPath)
if err != nil {
return err
}
if cs == nil {
return status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found repo %s", repoPath)
prefix, err = rs.getRelativeStorePath(cs)
if err != nil {
logger.WithError(err).Error("error getting file store path for chunk store")
return err
}
}
hashes, _ := remotestorage.ParseByteSlices(req.ChunkHashes)
numHashes += len(hashes)
locations, err := cs.GetChunkLocationsWithPaths(hashes)
if err != nil {
logger.WithError(err).Error("error getting chunk locations for hashes")
return err
}
var locs []*remotesapi.DownloadLoc
for loc, hashToRange := range locations {
if len(hashToRange) == 0 {
continue
}
numUrls += 1
numRanges += len(hashToRange)
var ranges []*remotesapi.RangeChunk
for h, r := range hashToRange {
hCpy := h
ranges = append(ranges, &remotesapi.RangeChunk{Hash: hCpy[:], Offset: r.Offset, Length: r.Length})
}
url, err := rs.getDownloadUrl(logger, md, prefix+"/"+loc)
if err != nil {
logger.Println("Failed to sign request", err)
return err
}
url := rs.getDownloadUrl(md, prefix+"/"+loc)
preurl := url.String()
url, err = rs.sealer.Seal(url)
if err != nil {
logger.Println("Failed to seal request", err)
logger.WithError(err).Error("error sealing download url")
return err
}
logger.Println("The URL is", preurl, "the ranges are", ranges, "sealed url", url.String())
logger.WithFields(logrus.Fields{
"url": preurl,
"ranges": ranges,
"sealed_url": url.String(),
}).Trace("generated sealed url")
getRange := &remotesapi.HttpGetRange{Url: url.String(), Ranges: ranges}
locs = append(locs, &remotesapi.DownloadLoc{Location: &remotesapi.DownloadLoc_HttpGetRange{HttpGetRange: getRange}})
@@ -286,13 +318,13 @@ func (rs *RemoteChunkStore) getHost(md metadata.MD) string {
return host
}
func (rs *RemoteChunkStore) getDownloadUrl(logger *logrus.Entry, md metadata.MD, path string) (*url.URL, error) {
func (rs *RemoteChunkStore) getDownloadUrl(md metadata.MD, path string) *url.URL {
host := rs.getHost(md)
return &url.URL{
Scheme: rs.httpScheme,
Host: host,
Path: path,
}, nil
}
}
func parseTableFileDetails(req *remotesapi.GetUploadLocsRequest) []*remotesapi.TableFileDetails {
@@ -316,20 +348,15 @@ func parseTableFileDetails(req *remotesapi.GetUploadLocsRequest) []*remotesapi.T
func (rs *RemoteChunkStore) GetUploadLocations(ctx context.Context, req *remotesapi.GetUploadLocsRequest) (*remotesapi.GetUploadLocsResponse, error) {
logger := getReqLogger(rs.lgr, "GetUploadLocations")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
cs, err := rs.getStore(logger, repoPath)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
_, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found repo %s", repoPath)
tfds := parseTableFileDetails(req)
md, _ := metadata.FromIncomingContext(ctx)
@@ -337,25 +364,30 @@ func (rs *RemoteChunkStore) GetUploadLocations(ctx context.Context, req *remotes
var locs []*remotesapi.UploadLoc
for _, tfd := range tfds {
h := hash.New(tfd.Id)
url, err := rs.getUploadUrl(logger, md, repoPath, tfd)
if err != nil {
return nil, status.Error(codes.Internal, "Failed to get upload Url.")
}
url := rs.getUploadUrl(md, repoPath, tfd)
url, err = rs.sealer.Seal(url)
if err != nil {
logger.WithError(err).Error("error sealing upload url")
return nil, status.Error(codes.Internal, "Failed to seal upload Url.")
}
loc := &remotesapi.UploadLoc_HttpPost{HttpPost: &remotesapi.HttpPostTableFile{Url: url.String()}}
locs = append(locs, &remotesapi.UploadLoc{TableFileHash: h[:], Location: loc})
logger.Printf("sending upload location for chunk %s: %s", h.String(), url.String())
logger.WithFields(logrus.Fields{
"table_file_hash": h.String(),
"url": url.String(),
}).Trace("sending upload location for table file")
}
logger = logger.WithFields(logrus.Fields{
"num_urls": len(locs),
})
return &remotesapi.GetUploadLocsResponse{Locs: locs}, nil
}
func (rs *RemoteChunkStore) getUploadUrl(logger *logrus.Entry, md metadata.MD, repoPath string, tfd *remotesapi.TableFileDetails) (*url.URL, error) {
func (rs *RemoteChunkStore) getUploadUrl(md metadata.MD, repoPath string, tfd *remotesapi.TableFileDetails) *url.URL {
fileID := hash.New(tfd.Id).String()
params := url.Values{}
params.Add("num_chunks", strconv.Itoa(int(tfd.NumChunks)))
@@ -366,53 +398,37 @@ func (rs *RemoteChunkStore) getUploadUrl(logger *logrus.Entry, md metadata.MD, r
Host: rs.getHost(md),
Path: fmt.Sprintf("%s/%s", repoPath, fileID),
RawQuery: params.Encode(),
}, nil
}
}
func (rs *RemoteChunkStore) Rebase(ctx context.Context, req *remotesapi.RebaseRequest) (*remotesapi.RebaseResponse, error) {
logger := getReqLogger(rs.lgr, "Rebase")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
cs, err := rs.getStore(logger, repoPath)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
_, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found %s", repoPath)
err = cs.Rebase(ctx)
if err != nil {
logger.Printf("error occurred during processing of Rebase rpc of %s details: %v", repoPath, err)
return nil, status.Errorf(codes.Internal, "failed to rebase: %v", err)
}
return &remotesapi.RebaseResponse{}, nil
}
func (rs *RemoteChunkStore) Root(ctx context.Context, req *remotesapi.RootRequest) (*remotesapi.RootResponse, error) {
logger := getReqLogger(rs.lgr, "Root")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
cs, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
h, err := cs.Root(ctx)
if err != nil {
logger.Printf("error occurred during processing of Root rpc of %s details: %v", repoPath, err)
logger.WithError(err).Error("error calling Root on chunk store.")
return nil, status.Error(codes.Internal, "Failed to get root")
}
@@ -421,20 +437,15 @@ func (rs *RemoteChunkStore) Root(ctx context.Context, req *remotesapi.RootReques
func (rs *RemoteChunkStore) Commit(ctx context.Context, req *remotesapi.CommitRequest) (*remotesapi.CommitResponse, error) {
logger := getReqLogger(rs.lgr, "Commit")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
cs, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found %s", repoPath)
//should validate
updates := make(map[string]int)
for _, cti := range req.ChunkTableInfo {
@@ -442,9 +453,8 @@ func (rs *RemoteChunkStore) Commit(ctx context.Context, req *remotesapi.CommitRe
}
err = cs.AddTableFilesToManifest(ctx, updates)
if err != nil {
logger.Printf("error occurred updating the manifest: %s", err.Error())
logger.WithError(err).Error("error calling AddTableFilesToManifest")
return nil, status.Errorf(codes.Internal, "manifest update error: %v", err)
}
@@ -453,36 +463,32 @@ func (rs *RemoteChunkStore) Commit(ctx context.Context, req *remotesapi.CommitRe
var ok bool
ok, err = cs.Commit(ctx, currHash, lastHash)
if err != nil {
logger.Printf("error occurred during processing of Commit of %s last %s curr: %s details: %v", repoPath, lastHash.String(), currHash.String(), err)
logger.WithError(err).WithFields(logrus.Fields{
"last_hash": lastHash.String(),
"curr_hash": currHash.String(),
}).Error("error calling Commit")
return nil, status.Errorf(codes.Internal, "failed to commit: %v", err)
}
logger.Printf("committed %s moved from %s -> %s", repoPath, lastHash.String(), currHash.String())
logger.Tracef("Commit success; moved from %s -> %s", lastHash.String(), currHash.String())
return &remotesapi.CommitResponse{Success: ok}, nil
}
func (rs *RemoteChunkStore) GetRepoMetadata(ctx context.Context, req *remotesapi.GetRepoMetadataRequest) (*remotesapi.GetRepoMetadataResponse, error) {
logger := getReqLogger(rs.lgr, "GetRepoMetadata")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
cs, err := rs.getOrCreateStore(logger, repoPath, req.ClientRepoFormat.NbfVersion)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
err = cs.Rebase(ctx)
cs, err := rs.getOrCreateStore(logger, repoPath, req.ClientRepoFormat.NbfVersion)
if err != nil {
return nil, err
}
size, err := cs.Size(ctx)
if err != nil {
logger.WithError(err).Error("error calling Size")
return nil, err
}
@@ -495,23 +501,18 @@ func (rs *RemoteChunkStore) GetRepoMetadata(ctx context.Context, req *remotesapi
func (rs *RemoteChunkStore) ListTableFiles(ctx context.Context, req *remotesapi.ListTableFilesRequest) (*remotesapi.ListTableFilesResponse, error) {
logger := getReqLogger(rs.lgr, "ListTableFiles")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
cs, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found repo %s", repoPath)
root, tables, appendixTables, err := cs.Sources(ctx)
if err != nil {
logger.WithError(err).Error("error getting chunk store Sources")
return nil, status.Error(codes.Internal, "failed to get sources")
}
@@ -519,14 +520,21 @@ func (rs *RemoteChunkStore) ListTableFiles(ctx context.Context, req *remotesapi.
tableFileInfo, err := getTableFileInfo(logger, md, rs, tables, req, cs)
if err != nil {
logger.WithError(err).Error("error getting table file info")
return nil, err
}
appendixTableFileInfo, err := getTableFileInfo(logger, md, rs, appendixTables, req, cs)
if err != nil {
logger.WithError(err).Error("error getting appendix table file info")
return nil, err
}
logger = logger.WithFields(logrus.Fields{
"num_table_files": len(tableFileInfo),
"num_appendix_table_files": len(appendixTableFileInfo),
})
resp := &remotesapi.ListTableFilesResponse{
RootHash: root[:],
TableFileInfo: tableFileInfo,
@@ -550,10 +558,7 @@ func getTableFileInfo(
}
appendixTableFileInfo := make([]*remotesapi.TableFileInfo, 0)
for _, t := range tableList {
url, err := rs.getDownloadUrl(logger, md, prefix+"/"+t.FileID())
if err != nil {
return nil, status.Error(codes.Internal, "failed to get download url for "+t.FileID())
}
url := rs.getDownloadUrl(md, prefix+"/"+t.FileID())
url, err = rs.sealer.Seal(url)
if err != nil {
return nil, status.Error(codes.Internal, "failed to get seal download url for "+t.FileID())
@@ -571,20 +576,15 @@ func getTableFileInfo(
// AddTableFiles updates the remote manifest with new table files without modifying the root hash.
func (rs *RemoteChunkStore) AddTableFiles(ctx context.Context, req *remotesapi.AddTableFilesRequest) (*remotesapi.AddTableFilesResponse, error) {
logger := getReqLogger(rs.lgr, "AddTableFiles")
defer func() { logger.Println("finished") }()
repoPath := getRepoPath(req)
logger = logger.WithField(RepoPathField, repoPath)
defer func() { logger.Info("finished") }()
cs, err := rs.getStore(logger, repoPath)
if err != nil {
return nil, err
}
if cs == nil {
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
logger.Printf("found %s", repoPath)
// should validate
updates := make(map[string]int)
for _, cti := range req.ChunkTableInfo {
@@ -592,12 +592,15 @@ func (rs *RemoteChunkStore) AddTableFiles(ctx context.Context, req *remotesapi.A
}
err = cs.AddTableFilesToManifest(ctx, updates)
if err != nil {
logger.Printf("error occurred updating the manifest: %s", err.Error())
logger.WithError(err).Error("error occurred updating the manifest")
return nil, status.Error(codes.Internal, "manifest update error")
}
logger = logger.WithFields(logrus.Fields{
"num_files": len(updates),
})
return &remotesapi.AddTableFilesResponse{Success: true}, nil
}
@@ -608,12 +611,16 @@ func (rs *RemoteChunkStore) getStore(logger *logrus.Entry, repoPath string) (Rem
func (rs *RemoteChunkStore) getOrCreateStore(logger *logrus.Entry, repoPath, nbfVerStr string) (RemoteSrvStore, error) {
cs, err := rs.csCache.Get(repoPath, nbfVerStr)
if err != nil {
logger.Printf("Failed to retrieve chunkstore for %s\n", repoPath)
logger.WithError(err).Error("Failed to retrieve chunkstore")
if errors.Is(err, ErrUnimplemented) {
return nil, status.Error(codes.Unimplemented, err.Error())
}
return nil, err
}
if cs == nil {
logger.Error("internal error getting chunk store; csCache.Get returned nil")
return nil, status.Error(codes.Internal, "Could not get chunkstore")
}
return cs, nil
}
@@ -628,7 +635,7 @@ func getReqLogger(lgr *logrus.Entry, method string) *logrus.Entry {
"method": method,
"request_num": strconv.Itoa(incReqId()),
})
lgr.Println("starting request")
lgr.Info("starting request")
return lgr
}
+62 -49
View File
@@ -63,16 +63,17 @@ func newFileHandler(lgr *logrus.Entry, dbCache DBCache, fs filesys.Filesys, read
func (fh filehandler) ServeHTTP(respWr http.ResponseWriter, req *http.Request) {
logger := getReqLogger(fh.lgr, req.Method+"_"+req.RequestURI)
defer func() { logger.Println("finished") }()
defer func() { logger.Info("finished") }()
var err error
req.URL, err = fh.sealer.Unseal(req.URL)
if err != nil {
logger.Printf("could not unseal incoming request URL: %s", err.Error())
logger.WithError(err).Warn("could not unseal incoming request URL")
respWr.WriteHeader(http.StatusBadRequest)
return
}
logger.Printf("unsealed url %s", req.URL.String())
logger = logger.WithField("unsealed_url", req.URL.String())
path := strings.TrimLeft(req.URL.Path, "/")
@@ -81,29 +82,29 @@ func (fh filehandler) ServeHTTP(respWr http.ResponseWriter, req *http.Request) {
case http.MethodGet:
path = filepath.Clean(path)
if strings.HasPrefix(path, "../") || strings.Contains(path, "/../") || strings.HasSuffix(path, "/..") {
logger.Println("bad request with .. for path", path)
logger.Warn("bad request with .. in URL path")
respWr.WriteHeader(http.StatusBadRequest)
return
}
i := strings.LastIndex(path, "/")
if i == -1 {
logger.Println("bad request with -1 LastIndex of '/' for path ", path)
logger.Warn("bad request with -1 LastIndex of '/' for path")
respWr.WriteHeader(http.StatusBadRequest)
return
}
_, ok := hash.MaybeParse(path[i+1:])
if !ok {
logger.Println("bad request with unparseable last path component", path[i+1:])
logger.WithField("last_path_component", path[i+1:]).Warn("bad request with unparseable last path component")
respWr.WriteHeader(http.StatusBadRequest)
return
}
abs, err := fh.fs.Abs(path)
if err != nil {
logger.Printf("could not get absolute path: %s", err.Error())
logger.WithError(err).Error("could not get absolute path")
respWr.WriteHeader(http.StatusInternalServerError)
return
}
statusCode = readTableFile(logger, abs, respWr, req.Header.Get("Range"))
logger, statusCode = readTableFile(logger, abs, respWr, req.Header.Get("Range"))
case http.MethodPost, http.MethodPut:
if fh.readOnly {
@@ -114,7 +115,7 @@ func (fh filehandler) ServeHTTP(respWr http.ResponseWriter, req *http.Request) {
i := strings.LastIndex(path, "/")
// a table file name is currently 32 characters, plus the '/' is 33.
if i < 0 || len(path[i:]) != 33 {
logger.Printf("response to: %v method: %v http response code: %v", req.RequestURI, req.Method, http.StatusNotFound)
logger = logger.WithField("status", http.StatusNotFound)
respWr.WriteHeader(http.StatusNotFound)
return
}
@@ -125,42 +126,48 @@ func (fh filehandler) ServeHTTP(respWr http.ResponseWriter, req *http.Request) {
q := req.URL.Query()
ncs := q.Get("num_chunks")
if ncs == "" {
logger.Printf("response to: %v method: %v http response code: %v: num_chunks parameter not provided", req.RequestURI, req.Method, http.StatusBadRequest)
logger = logger.WithField("status", http.StatusBadRequest)
logger.Warn("bad request: num_chunks parameter not provided")
respWr.WriteHeader(http.StatusBadRequest)
return
}
num_chunks, err := strconv.Atoi(ncs)
if err != nil {
logger.Printf("response to: %v method: %v http response code: %v: num_chunks parameter did not parse: %v", req.RequestURI, req.Method, http.StatusBadRequest, err)
logger = logger.WithField("status", http.StatusBadRequest)
logger.WithError(err).Warn("bad request: num_chunks parameter did not parse")
respWr.WriteHeader(http.StatusBadRequest)
return
}
cls := q.Get("content_length")
if cls == "" {
logger.Printf("response to: %v method: %v http response code: %v: content_length parameter not provided", req.RequestURI, req.Method, http.StatusBadRequest)
logger = logger.WithField("status", http.StatusBadRequest)
logger.Warn("bad request: content_length parameter not provided")
respWr.WriteHeader(http.StatusBadRequest)
return
}
content_length, err := strconv.Atoi(cls)
if err != nil {
logger.Printf("response to: %v method: %v http response code: %v: content_length parameter did not parse: %v", req.RequestURI, req.Method, http.StatusBadRequest, err)
logger = logger.WithField("status", http.StatusBadRequest)
logger.WithError(err).Warn("bad request: content_length parameter did not parse")
respWr.WriteHeader(http.StatusBadRequest)
return
}
chs := q.Get("content_hash")
if chs == "" {
logger.Printf("response to: %v method: %v http response code: %v: content_hash parameter not provided", req.RequestURI, req.Method, http.StatusBadRequest)
logger = logger.WithField("status", http.StatusBadRequest)
logger.Warn("bad request: content_hash parameter not provided")
respWr.WriteHeader(http.StatusBadRequest)
return
}
content_hash, err := base64.RawURLEncoding.DecodeString(chs)
if err != nil {
logger.Printf("response to: %v method: %v http response code: %v: content_hash parameter did not parse: %v", req.RequestURI, req.Method, http.StatusBadRequest, err)
logger = logger.WithField("status", http.StatusBadRequest)
logger.WithError(err).Warn("bad request: content_hash parameter did not parse")
respWr.WriteHeader(http.StatusBadRequest)
return
}
statusCode = writeTableFile(req.Context(), logger, fh.dbCache, filepath, file, num_chunks, content_hash, uint64(content_length), req.Body)
logger, statusCode = writeTableFile(req.Context(), logger, fh.dbCache, filepath, file, num_chunks, content_hash, uint64(content_length), req.Body)
}
if statusCode != -1 {
@@ -168,21 +175,24 @@ func (fh filehandler) ServeHTTP(respWr http.ResponseWriter, req *http.Request) {
}
}
func readTableFile(logger *logrus.Entry, path string, respWr http.ResponseWriter, rangeStr string) int {
func readTableFile(logger *logrus.Entry, path string, respWr http.ResponseWriter, rangeStr string) (*logrus.Entry, int) {
var r io.ReadCloser
var readSize int64
var fileErr error
{
if rangeStr == "" {
logger.Println("going to read entire file", path)
logger = logger.WithField("whole_file", true)
r, readSize, fileErr = getFileReader(path)
} else {
offset, length, err := offsetAndLenFromRange(rangeStr)
if err != nil {
logger.Println(err.Error())
return http.StatusBadRequest
return logger, http.StatusBadRequest
}
logger.Printf("going to read file %s at offset %d, length %d", path, offset, length)
logger = logger.WithFields(logrus.Fields{
"read_offset": offset,
"read_length": length,
})
readSize = length
r, fileErr = getFileReaderAt(path, offset, length)
}
@@ -190,36 +200,36 @@ func readTableFile(logger *logrus.Entry, path string, respWr http.ResponseWriter
if fileErr != nil {
logger.Println(fileErr.Error())
if errors.Is(fileErr, os.ErrNotExist) {
return http.StatusNotFound
logger = logger.WithField("status", http.StatusNotFound)
return logger, http.StatusNotFound
} else if errors.Is(fileErr, ErrReadOutOfBounds) {
return http.StatusBadRequest
logger = logger.WithField("status", http.StatusBadRequest)
logger.Warn("bad request: offset out of bounds for path")
return logger, http.StatusBadRequest
}
return http.StatusInternalServerError
logger = logger.WithError(fileErr)
return logger, http.StatusInternalServerError
}
defer func() {
err := r.Close()
if err != nil {
err = fmt.Errorf("failed to close file at path %s: %w", path, err)
logger.Println(err.Error())
logger.WithError(err).Warn("failed to close file")
}
}()
logger.Printf("opened file at path %s, going to read %d bytes", path, readSize)
n, err := io.Copy(respWr, r)
if err != nil {
err = fmt.Errorf("failed to write data to response writer: %w", err)
logger.Println(err.Error())
return http.StatusInternalServerError
logger = logger.WithField("status", http.StatusInternalServerError)
logger.WithError(err).Error("error copying data to response writer")
return logger, http.StatusInternalServerError
}
if n != readSize {
logger.Printf("wanted to write %d bytes from file (%s) but only wrote %d", readSize, path, n)
return http.StatusInternalServerError
logger = logger.WithField("status", http.StatusInternalServerError)
logger.WithField("copied_size", n).Error("failed to copy all bytes to response")
return logger, http.StatusInternalServerError
}
logger.Printf("wrote %d bytes", n)
return -1
return logger, -1
}
type uploadreader struct {
@@ -257,19 +267,19 @@ func (u *uploadreader) Close() error {
return nil
}
func writeTableFile(ctx context.Context, logger *logrus.Entry, dbCache DBCache, path, fileId string, numChunks int, contentHash []byte, contentLength uint64, body io.ReadCloser) int {
func writeTableFile(ctx context.Context, logger *logrus.Entry, dbCache DBCache, path, fileId string, numChunks int, contentHash []byte, contentLength uint64, body io.ReadCloser) (*logrus.Entry, int) {
_, ok := hash.MaybeParse(fileId)
if !ok {
logger.Println(fileId, "is not a valid hash")
return http.StatusBadRequest
logger = logger.WithField("status", http.StatusBadRequest)
logger.Warnf("%s is not a valid hash", fileId)
return logger, http.StatusBadRequest
}
logger.Println(fileId, "is valid")
cs, err := dbCache.Get(path, types.Format_Default.VersionString())
if err != nil {
logger.Println("failed to get", path, "repository:", err.Error())
return http.StatusInternalServerError
logger = logger.WithField("status", http.StatusInternalServerError)
logger.WithError(err).Error("failed to get repository")
return logger, http.StatusInternalServerError
}
err = cs.WriteTableFile(ctx, fileId, numChunks, contentHash, func() (io.ReadCloser, uint64, error) {
@@ -286,18 +296,21 @@ func writeTableFile(ctx context.Context, logger *logrus.Entry, dbCache DBCache,
if err != nil {
if errors.Is(err, errBodyLengthTFDMismatch) {
logger.Println("bad write file request for", fileId, ": body length mismatch")
return http.StatusBadRequest
logger = logger.WithField("status", http.StatusBadRequest)
logger.Warn("bad request: body length mismatch")
return logger, http.StatusBadRequest
}
if errors.Is(err, errBodyHashTFDMismatch) {
logger.Println("bad write file request for", fileId, ": body hash mismatch")
return http.StatusBadRequest
logger = logger.WithField("status", http.StatusBadRequest)
logger.Warn("bad request: body hash mismatch")
return logger, http.StatusBadRequest
}
logger.Println("failed to read body", err.Error())
return http.StatusInternalServerError
logger = logger.WithField("status", http.StatusInternalServerError)
logger.WithError(err).Error("failed to write upload to table file")
return logger, http.StatusInternalServerError
}
return http.StatusOK
return logger, http.StatusOK
}
func offsetAndLenFromRange(rngStr string) (int64, int64, error) {
@@ -110,6 +110,7 @@ type DoltChunkStore struct {
repoPath string
repoToken *atomic.Value // string
host string
root hash.Hash
csClient remotesapi.ChunkStoreServiceClient
cache ChunkCache
metadata *remotesapi.GetRepoMetadataResponse
@@ -146,10 +147,15 @@ func NewDoltChunkStoreFromPath(ctx context.Context, nbf *types.NomsBinFormat, pa
return nil, err
}
repoToken := new(atomic.Value)
if metadata.RepoToken != "" {
repoToken.Store(metadata.RepoToken)
}
cs := &DoltChunkStore{
repoId: repoId,
repoPath: path,
repoToken: new(atomic.Value),
repoToken: repoToken,
host: host,
csClient: csClient,
cache: newMapChunkCache(),
@@ -158,6 +164,10 @@ func NewDoltChunkStoreFromPath(ctx context.Context, nbf *types.NomsBinFormat, pa
httpFetcher: globalHttpFetcher,
concurrency: defaultConcurrency,
}
err = cs.loadRoot(ctx)
if err != nil {
return nil, err
}
return cs, nil
}
@@ -167,6 +177,7 @@ func (dcs *DoltChunkStore) WithHTTPFetcher(fetcher HTTPFetcher) *DoltChunkStore
repoPath: dcs.repoPath,
repoToken: new(atomic.Value),
host: dcs.host,
root: dcs.root,
csClient: dcs.csClient,
cache: dcs.cache,
metadata: dcs.metadata,
@@ -183,6 +194,7 @@ func (dcs *DoltChunkStore) WithNoopChunkCache() *DoltChunkStore {
repoPath: dcs.repoPath,
repoToken: new(atomic.Value),
host: dcs.host,
root: dcs.root,
csClient: dcs.csClient,
cache: noopChunkCache,
metadata: dcs.metadata,
@@ -200,6 +212,7 @@ func (dcs *DoltChunkStore) WithChunkCache(cache ChunkCache) *DoltChunkStore {
repoPath: dcs.repoPath,
repoToken: new(atomic.Value),
host: dcs.host,
root: dcs.root,
csClient: dcs.csClient,
cache: cache,
metadata: dcs.metadata,
@@ -217,6 +230,7 @@ func (dcs *DoltChunkStore) WithDownloadConcurrency(concurrency ConcurrencyParams
repoPath: dcs.repoPath,
repoToken: new(atomic.Value),
host: dcs.host,
root: dcs.root,
csClient: dcs.csClient,
cache: dcs.cache,
metadata: dcs.metadata,
@@ -776,11 +790,32 @@ func (dcs *DoltChunkStore) HasMany(ctx context.Context, hashes hash.HashSet) (ha
return absent, nil
}
func (dcs *DoltChunkStore) errorIfDangling(ctx context.Context, addrs hash.HashSet) error {
absent, err := dcs.HasMany(ctx, addrs)
if err != nil {
return err
}
if len(absent) != 0 {
s := absent.String()
return fmt.Errorf("Found dangling references to %s", s)
}
return nil
}
// Put caches c. Upon return, c must be visible to
// subsequent Get and Has calls, but must not be persistent until a call
// to Flush(). Put may be called concurrently with other calls to Put(),
// Get(), GetMany(), Has() and HasMany().
func (dcs *DoltChunkStore) Put(ctx context.Context, c chunks.Chunk) error {
func (dcs *DoltChunkStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCb) error {
addrs, err := getAddrs(ctx, c)
if err != nil {
return err
}
err = dcs.errorIfDangling(ctx, addrs)
if err != nil {
return err
}
cc := nbs.ChunkToCompressedChunk(c)
if dcs.cache.Put([]nbs.CompressedChunk{cc}) {
return ErrCacheCapacityExceeded
@@ -796,17 +831,10 @@ func (dcs *DoltChunkStore) Version() string {
// Rebase brings this ChunkStore into sync with the persistent storage's
// current root.
func (dcs *DoltChunkStore) Rebase(ctx context.Context) error {
id, token := dcs.getRepoId()
req := &remotesapi.RebaseRequest{RepoId: id, RepoToken: token, RepoPath: dcs.repoPath}
resp, err := dcs.csClient.Rebase(ctx, req)
err := dcs.loadRoot(ctx)
if err != nil {
return NewRpcError(err, "Rebase", dcs.host, req)
return err
}
if resp.RepoToken != "" {
dcs.repoToken.Store(token)
}
return dcs.refreshRepoMetadata(ctx)
}
@@ -833,18 +861,21 @@ func (dcs *DoltChunkStore) refreshRepoMetadata(ctx context.Context) error {
// Root returns the root of the database as of the time the ChunkStore
// was opened or the most recent call to Rebase.
func (dcs *DoltChunkStore) Root(ctx context.Context) (hash.Hash, error) {
return dcs.root, nil
}
func (dcs *DoltChunkStore) loadRoot(ctx context.Context) error {
id, token := dcs.getRepoId()
req := &remotesapi.RootRequest{RepoId: id, RepoToken: token, RepoPath: dcs.repoPath}
resp, err := dcs.csClient.Root(ctx, req)
if err != nil {
return hash.Hash{}, NewRpcError(err, "Root", dcs.host, req)
return NewRpcError(err, "Root", dcs.host, req)
}
if resp.RepoToken != "" {
dcs.repoToken.Store(resp.RepoToken)
}
return hash.New(resp.RootHash), nil
dcs.root = hash.New(resp.RootHash)
return nil
}
// Commit atomically attempts to persist all novel Chunks and update the
@@ -878,6 +909,10 @@ func (dcs *DoltChunkStore) Commit(ctx context.Context, current, last hash.Hash)
if err != nil {
return false, NewRpcError(err, "Commit", dcs.host, req)
}
err = dcs.loadRoot(ctx)
if err != nil {
return false, NewRpcError(err, "Commit", dcs.host, req)
}
return resp.Success, dcs.refreshRepoMetadata(ctx)
}
@@ -220,17 +220,19 @@ func (h *commithook) attemptReplicate(ctx context.Context) {
}
lgr.Tracef("cluster/commithook: pushing chunks for root hash %v to destDB", toPush.String())
err := destDB.PullChunks(ctx, h.tempDir, h.srcDB, []hash.Hash{toPush}, nil, nil)
err := destDB.PullChunks(ctx, h.tempDir, h.srcDB, []hash.Hash{toPush}, nil)
if err == nil {
lgr.Tracef("cluster/commithook: successfully pushed chunks, setting root")
datasDB := doltdb.HackDatasDatabaseFromDoltDB(destDB)
cs := datas.ChunkStoreFromDatabase(datasDB)
var curRootHash hash.Hash
if curRootHash, err = cs.Root(ctx); err == nil {
var ok bool
ok, err = cs.Commit(ctx, toPush, curRootHash)
if err == nil && !ok {
err = errDestDBRootHashMoved
if err = cs.Rebase(ctx); err == nil {
if curRootHash, err = cs.Root(ctx); err == nil {
var ok bool
ok, err = cs.Commit(ctx, toPush, curRootHash)
if err == nil && !ok {
err = errDestDBRootHashMoved
}
}
}
}
@@ -61,6 +61,10 @@ func NewClusterDatabase(p ClusterStatusProvider) sql.Database {
// Implement StoredProcedureDatabase so that external stored procedures are available.
var _ sql.StoredProcedureDatabase = database{}
func (database) GetStoredProcedure(ctx *sql.Context, name string) (sql.StoredProcedureDetails, bool, error) {
return sql.StoredProcedureDetails{}, false, nil
}
func (database) GetStoredProcedures(ctx *sql.Context) ([]sql.StoredProcedureDetails, error) {
return nil, nil
}
+64 -44
View File
@@ -24,6 +24,8 @@ import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/mysql_db"
"github.com/dolthub/go-mysql-server/sql/parse"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/go-mysql-server/sql/types"
"gopkg.in/src-d/go-errors.v1"
@@ -1062,11 +1064,11 @@ func (db Database) Flush(ctx *sql.Context) error {
return db.SetRoot(ctx, ws.WorkingRoot())
}
// GetView implements sql.ViewDatabase
func (db Database) GetView(ctx *sql.Context, viewName string) (string, bool, error) {
// GetViewDefinition implements sql.ViewDatabase
func (db Database) GetViewDefinition(ctx *sql.Context, viewName string) (sql.ViewDefinition, bool, error) {
root, err := db.GetRoot(ctx)
if err != nil {
return "", false, err
return sql.ViewDefinition{}, false, err
}
lwrViewName := strings.ToLower(viewName)
@@ -1074,62 +1076,79 @@ func (db Database) GetView(ctx *sql.Context, viewName string) (string, bool, err
case strings.HasPrefix(lwrViewName, doltdb.DoltBlameViewPrefix):
tableName := lwrViewName[len(doltdb.DoltBlameViewPrefix):]
view, err := dtables.NewBlameView(ctx, tableName, root)
blameViewTextDef, err := dtables.NewBlameView(ctx, tableName, root)
if err != nil {
return "", false, err
return sql.ViewDefinition{}, false, err
}
return view, true, nil
return sql.ViewDefinition{Name: viewName, TextDefinition: blameViewTextDef, CreateViewStatement: fmt.Sprintf("CREATE VIEW %s AS %s", viewName, blameViewTextDef)}, true, nil
}
key, err := doltdb.NewDataCacheKey(root)
if err != nil {
return "", false, err
return sql.ViewDefinition{}, false, err
}
ds := dsess.DSessFromSess(ctx.Session)
dbState, _, err := ds.LookupDbState(ctx, db.name)
if err != nil {
return "", false, err
return sql.ViewDefinition{}, false, err
}
if dbState.SessionCache().ViewsCached(key) {
view, ok := dbState.SessionCache().GetCachedView(key, viewName)
view, ok := dbState.SessionCache().GetCachedViewDefinition(key, viewName)
return view, ok, nil
}
tbl, ok, err := db.GetTableInsensitive(ctx, doltdb.SchemasTableName)
if err != nil {
return "", false, err
return sql.ViewDefinition{}, false, err
}
if !ok {
dbState.SessionCache().CacheViews(key, nil, nil)
return "", false, nil
dbState.SessionCache().CacheViews(key, nil)
return sql.ViewDefinition{}, false, nil
}
fragments, err := getSchemaFragmentsOfType(ctx, tbl.(*WritableDoltTable), viewFragment)
views, viewDef, found, err := getViewDefinitionFromSchemaFragmentsOfView(ctx, tbl.(*WritableDoltTable), viewName)
if err != nil {
return "", false, err
return sql.ViewDefinition{}, false, err
}
found := false
viewDef := ""
viewNames := make([]string, len(fragments))
viewDefs := make([]string, len(fragments))
for i, fragment := range fragments {
if strings.ToLower(fragment.name) == strings.ToLower(viewName) {
found = true
viewDef = fragments[i].fragment
}
viewNames[i] = fragments[i].name
viewDefs[i] = fragments[i].fragment
}
dbState.SessionCache().CacheViews(key, viewNames, viewDefs)
dbState.SessionCache().CacheViews(key, views)
return viewDef, found, nil
}
func getViewDefinitionFromSchemaFragmentsOfView(ctx *sql.Context, tbl *WritableDoltTable, viewName string) ([]sql.ViewDefinition, sql.ViewDefinition, bool, error) {
fragments, err := getSchemaFragmentsOfType(ctx, tbl, viewFragment)
if err != nil {
return nil, sql.ViewDefinition{}, false, err
}
var found = false
var viewDef sql.ViewDefinition
var views = make([]sql.ViewDefinition, len(fragments))
for i, fragment := range fragments {
cv, err := parse.Parse(ctx, fragments[i].fragment)
if err != nil {
return nil, sql.ViewDefinition{}, false, err
}
createView, ok := cv.(*plan.CreateView)
if ok {
views[i] = sql.ViewDefinition{Name: fragments[i].name, TextDefinition: createView.Definition.TextDefinition, CreateViewStatement: fragments[i].fragment}
} else {
views[i] = sql.ViewDefinition{Name: fragments[i].name, TextDefinition: fragments[i].fragment, CreateViewStatement: fmt.Sprintf("CREATE VIEW %s AS %s", fragments[i].name, fragments[i].fragment)}
}
if strings.ToLower(fragment.name) == strings.ToLower(viewName) {
found = true
viewDef = views[i]
}
}
return views, viewDef, found, nil
}
// AllViews implements sql.ViewDatabase
func (db Database) AllViews(ctx *sql.Context) ([]sql.ViewDefinition, error) {
tbl, ok, err := db.GetTableInsensitive(ctx, doltdb.SchemasTableName)
@@ -1140,18 +1159,7 @@ func (db Database) AllViews(ctx *sql.Context) ([]sql.ViewDefinition, error) {
return nil, nil
}
frags, err := getSchemaFragmentsOfType(ctx, tbl.(*WritableDoltTable), viewFragment)
if err != nil {
return nil, err
}
var views []sql.ViewDefinition
for _, frag := range frags {
views = append(views, sql.ViewDefinition{
Name: frag.name,
TextDefinition: frag.fragment,
})
}
views, _, _, err := getViewDefinitionFromSchemaFragmentsOfView(ctx, tbl.(*WritableDoltTable), "")
if err != nil {
return nil, err
}
@@ -1162,9 +1170,9 @@ func (db Database) AllViews(ctx *sql.Context) ([]sql.ViewDefinition, error) {
// CreateView implements sql.ViewCreator. Persists the view in the dolt database, so
// it can exist in a sql session later. Returns sql.ErrExistingView if a view
// with that name already exists.
func (db Database) CreateView(ctx *sql.Context, name string, definition string) error {
func (db Database) CreateView(ctx *sql.Context, name string, selectStatement, createViewStmt string) error {
err := sql.ErrExistingView.New(db.name, name)
return db.addFragToSchemasTable(ctx, "view", name, definition, time.Unix(0, 0).UTC(), err)
return db.addFragToSchemasTable(ctx, "view", name, createViewStmt, time.Unix(0, 0).UTC(), err)
}
// DropView implements sql.ViewDropper. Removes a view from persistence in the
@@ -1222,9 +1230,21 @@ func (db Database) DropTrigger(ctx *sql.Context, name string) error {
return db.dropFragFromSchemasTable(ctx, "trigger", name, sql.ErrTriggerDoesNotExist.New(name))
}
// GetStoredProcedure implements sql.StoredProcedureDatabase.
func (db Database) GetStoredProcedure(ctx *sql.Context, name string) (sql.StoredProcedureDetails, bool, error) {
procedures, err := DoltProceduresGetAll(ctx, db, strings.ToLower(name))
if err != nil {
return sql.StoredProcedureDetails{}, false, nil
}
if len(procedures) == 1 {
return procedures[0], true, nil
}
return sql.StoredProcedureDetails{}, false, nil
}
// GetStoredProcedures implements sql.StoredProcedureDatabase.
func (db Database) GetStoredProcedures(ctx *sql.Context) ([]sql.StoredProcedureDetails, error) {
return DoltProceduresGetAll(ctx, db)
return DoltProceduresGetAll(ctx, db, "")
}
// SaveStoredProcedure implements sql.StoredProcedureDatabase.
@@ -262,6 +262,9 @@ func (p DoltDatabaseProvider) attemptCloneReplica(ctx *sql.Context, dbName strin
func (p DoltDatabaseProvider) HasDatabase(ctx *sql.Context, name string) bool {
_, err := p.Database(ctx, name)
if err != nil {
ctx.GetLogger().Errorf(err.Error())
}
return err == nil
}
@@ -416,7 +419,7 @@ func (p DoltDatabaseProvider) CreateCollatedDatabase(ctx *sql.Context, name stri
type InitDatabaseHook func(ctx *sql.Context, pro DoltDatabaseProvider, name string, env *env.DoltEnv) error
// configureReplication sets up replication for a newly created database as necessary
// ConfigureReplicationDatabaseHook sets up replication for a newly created database as necessary
// TODO: consider the replication heads / all heads setting
func ConfigureReplicationDatabaseHook(ctx *sql.Context, p DoltDatabaseProvider, name string, newEnv *env.DoltEnv) error {
_, replicationRemoteName, _ := sql.SystemVariables.GetGlobal(dsess.ReplicateToRemote)
@@ -1007,20 +1010,90 @@ func isBranch(ctx context.Context, db SqlDatabase, branchName string, dialer dbf
return "", false, fmt.Errorf("unrecognized type of database %T", db)
}
brName, branchExists, err := isLocalBranch(ctx, ddbs, branchName)
if err != nil {
return "", false, err
}
if branchExists {
return brName, true, nil
}
brName, branchExists, err = isRemoteBranch(ctx, db, ddbs, branchName)
if err != nil {
return "", false, err
}
if branchExists {
return brName, true, nil
}
return "", false, nil
}
func isLocalBranch(ctx context.Context, ddbs []*doltdb.DoltDB, branchName string) (string, bool, error) {
for _, ddb := range ddbs {
branchName, branchExists, err := ddb.HasBranch(ctx, branchName)
brName, branchExists, err := ddb.HasBranch(ctx, branchName)
if err != nil {
return "", false, err
}
if branchExists {
return branchName, true, nil
return brName, true, nil
}
}
return "", false, nil
}
// isRemoteBranch is called when the branch in connection string is not available as a local branch, so it searches
// for a remote tracking branch. If there is only one match, it creates a new local branch from the remote tracking
// branch and sets its upstream to it.
func isRemoteBranch(ctx context.Context, srcDB SqlDatabase, ddbs []*doltdb.DoltDB, branchName string) (string, bool, error) {
for _, ddb := range ddbs {
bn, branchExists, remoteRef, err := ddb.HasRemoteTrackingBranch(ctx, branchName)
if err != nil {
return "", false, err
}
if branchExists {
err = createLocalBranchFromRemoteTrackingBranch(ctx, srcDB.DbData(), ddb, branchName, remoteRef)
if err != nil {
return "", false, err
}
return bn, true, nil
}
}
return "", false, nil
}
// createLocalBranchFromRemoteTrackingBranch creates a new local branch from given remote tracking branch
// and sets its upstream to it.
func createLocalBranchFromRemoteTrackingBranch(ctx context.Context, dbData env.DbData, ddb *doltdb.DoltDB, branchName string, remoteRef ref.RemoteRef) error {
startPt := remoteRef.GetPath()
err := actions.CreateBranchOnDB(ctx, ddb, branchName, startPt, false, remoteRef)
if err != nil {
return err
}
// at this point the branch is created on db
branchRef := ref.NewBranchRef(branchName)
remote := remoteRef.GetRemote()
refSpec, err := ref.ParseRefSpecForRemote(remote, remoteRef.GetBranch())
if err != nil {
return fmt.Errorf("%w: '%s'", err, remote)
}
src := refSpec.SrcRef(branchRef)
dest := refSpec.DestRef(src)
return dbData.Rsw.UpdateBranch(branchRef.GetPath(), env.BranchConfig{
Merge: ref.MarshalableRef{
Ref: dest,
},
Remote: remote,
})
}
// isTag returns whether a tag with the given name is in scope for the database given
func isTag(ctx context.Context, db SqlDatabase, tagName string, dialer dbfactory.GRPCDialProvider) (bool, error) {
var ddbs []*doltdb.DoltDB
@@ -316,8 +316,7 @@ func validateConstraintViolations(ctx *sql.Context, before, after *doltdb.RootVa
return err
}
// todo: this is an expensive way to compute this
_, violators, err := merge.AddForeignKeyViolations(ctx, after, before, set.NewStrSet(tables), hash.Of(nil))
violators, err := merge.GetForeignKeyViolatedTables(ctx, after, before, set.NewStrSet(tables))
if err != nil {
return err
}
@@ -192,45 +192,22 @@ func pullerProgFunc(ctx context.Context, statsCh <-chan pull.Stats) {
}
// TODO: remove this as it does not do anything useful
func progFunc(ctx context.Context, progChan <-chan pull.PullProgress) {
for {
if ctx.Err() != nil {
return
}
select {
case <-ctx.Done():
return
case <-progChan:
default:
}
}
}
// TODO: remove this as it does not do anything useful
func runProgFuncs(ctx context.Context) (*sync.WaitGroup, chan pull.PullProgress, chan pull.Stats) {
func runProgFuncs(ctx context.Context) (*sync.WaitGroup, chan pull.Stats) {
statsCh := make(chan pull.Stats)
progChan := make(chan pull.PullProgress)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
progFunc(ctx, progChan)
}()
wg.Add(1)
go func() {
defer wg.Done()
pullerProgFunc(ctx, statsCh)
}()
return wg, progChan, statsCh
return wg, statsCh
}
// TODO: remove this as it does not do anything useful
func stopProgFuncs(cancel context.CancelFunc, wg *sync.WaitGroup, progChan chan pull.PullProgress, statsCh chan pull.Stats) {
func stopProgFuncs(cancel context.CancelFunc, wg *sync.WaitGroup, statsCh chan pull.Stats) {
cancel()
close(progChan)
close(statsCh)
wg.Wait()
}
@@ -28,7 +28,7 @@ import (
type SessionCache struct {
indexes map[doltdb.DataCacheKey]map[string][]sql.Index
tables map[doltdb.DataCacheKey]map[string]sql.Table
views map[doltdb.DataCacheKey]map[string]string
views map[doltdb.DataCacheKey]map[string]sql.ViewDefinition
mu sync.RWMutex
}
@@ -125,23 +125,23 @@ func (c *SessionCache) GetCachedTable(key doltdb.DataCacheKey, tableName string)
}
// CacheViews caches all views in a database for the cache key given
func (c *SessionCache) CacheViews(key doltdb.DataCacheKey, viewNames []string, viewDefs []string) {
func (c *SessionCache) CacheViews(key doltdb.DataCacheKey, views []sql.ViewDefinition) {
c.mu.Lock()
defer c.mu.Unlock()
if c.views == nil {
c.views = make(map[doltdb.DataCacheKey]map[string]string)
c.views = make(map[doltdb.DataCacheKey]map[string]sql.ViewDefinition)
}
viewsForKey, ok := c.views[key]
if !ok {
viewsForKey = make(map[string]string)
viewsForKey = make(map[string]sql.ViewDefinition)
c.views[key] = viewsForKey
}
for i := range viewNames {
viewName := strings.ToLower(viewNames[i])
viewsForKey[viewName] = viewDefs[i]
for i := range views {
viewName := strings.ToLower(views[i].Name)
viewsForKey[viewName] = views[i]
}
}
@@ -158,19 +158,19 @@ func (c *SessionCache) ViewsCached(key doltdb.DataCacheKey) bool {
return ok
}
// GetCachedView returns the cached view named, and whether the cache was present
func (c *SessionCache) GetCachedView(key doltdb.DataCacheKey, viewName string) (string, bool) {
// GetCachedViewDefinition returns the cached view named, and whether the cache was present
func (c *SessionCache) GetCachedViewDefinition(key doltdb.DataCacheKey, viewName string) (sql.ViewDefinition, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
viewName = strings.ToLower(viewName)
if c.views == nil {
return "", false
return sql.ViewDefinition{}, false
}
viewsForKey, ok := c.views[key]
if !ok {
return "", false
return sql.ViewDefinition{}, false
}
table, ok := viewsForKey[viewName]
@@ -47,7 +47,7 @@ var skipPrepared bool
// SkipPreparedsCount is used by the "ci-check-repo CI workflow
// as a reminder to consider prepareds when adding a new
// enginetest suite.
const SkipPreparedsCount = 83
const SkipPreparedsCount = 84
const skipPreparedFlag = "DOLT_SKIP_PREPARED_ENGINETESTS"
@@ -717,6 +717,12 @@ func TestStoredProcedures(t *testing.T) {
enginetest.TestStoredProcedures(t, newDoltHarness(t))
}
func TestCallAsOf(t *testing.T) {
for _, script := range DoltCallAsOf {
enginetest.TestScript(t, newDoltHarness(t), script)
}
}
func TestLargeJsonObjects(t *testing.T) {
SkipByDefaultInCI(t)
harness := newDoltHarness(t)
@@ -746,8 +746,8 @@ var DoltScripts = []queries.ScriptTest{
{
Query: "SELECT type, name, fragment, id FROM dolt_schemas ORDER BY 1, 2",
Expected: []sql.Row{
{"view", "view1", "SELECT v1 FROM viewtest", int64(1)},
{"view", "view2", "SELECT v2 FROM viewtest", int64(2)},
{"view", "view1", "CREATE VIEW view1 AS SELECT v1 FROM viewtest", int64(1)},
{"view", "view2", "CREATE VIEW view2 AS SELECT v2 FROM viewtest", int64(2)},
},
},
},
@@ -3421,3 +3421,356 @@ var DoltIndexPrefixScripts = []queries.ScriptTest{
},
},
}
// DoltCallAsOf are tests of using CALL ... AS OF using commits
var DoltCallAsOf = []queries.ScriptTest{
{
Name: "Database syntax properly handles inter-CALL communication",
SetUpScript: []string{
`CREATE PROCEDURE p1()
BEGIN
DECLARE str VARCHAR(20);
CALL p2(str);
SET str = CONCAT('a', str);
SELECT str;
END`,
`CREATE PROCEDURE p2(OUT param VARCHAR(20))
BEGIN
SET param = 'b';
END`,
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'First procedures');",
"CALL DOLT_BRANCH('p12');",
"DROP PROCEDURE p1;",
"DROP PROCEDURE p2;",
`CREATE PROCEDURE p1()
BEGIN
DECLARE str VARCHAR(20);
CALL p2(str);
SET str = CONCAT('c', str);
SELECT str;
END`,
`CREATE PROCEDURE p2(OUT param VARCHAR(20))
BEGIN
SET param = 'd';
END`,
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'Second procedures');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL p1();",
Expected: []sql.Row{{"cd"}},
},
{
Query: "CALL `mydb/main`.p1();",
Expected: []sql.Row{{"cd"}},
},
{
Query: "CALL `mydb/p12`.p1();",
Expected: []sql.Row{{"ab"}},
},
},
},
{
Name: "CALL ... AS OF references historic data through nested calls",
SetUpScript: []string{
"CREATE TABLE test (v1 BIGINT);",
"INSERT INTO test VALUES (1);",
`CREATE PROCEDURE p1()
BEGIN
CALL p2();
END`,
`CREATE PROCEDURE p2()
BEGIN
SELECT * FROM test;
END`,
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
"UPDATE test SET v1 = 2;",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
"UPDATE test SET v1 = 3;",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
"UPDATE test SET v1 = 4;",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL p1();",
Expected: []sql.Row{{4}},
},
{
Query: "CALL p1() AS OF 'HEAD';",
Expected: []sql.Row{{4}},
},
{
Query: "CALL p1() AS OF 'HEAD~1';",
Expected: []sql.Row{{3}},
},
{
Query: "CALL p1() AS OF 'HEAD~2';",
Expected: []sql.Row{{2}},
},
{
Query: "CALL p1() AS OF 'HEAD~3';",
Expected: []sql.Row{{1}},
},
},
},
{
Name: "CALL ... AS OF doesn't overwrite nested CALL ... AS OF",
SetUpScript: []string{
"CREATE TABLE myhistorytable (pk BIGINT PRIMARY KEY, s TEXT);",
"INSERT INTO myhistorytable VALUES (1, 'first row, 1'), (2, 'second row, 1'), (3, 'third row, 1');",
"CREATE PROCEDURE p1() BEGIN CALL p2(); END",
"CREATE PROCEDURE p1a() BEGIN CALL p2() AS OF 'HEAD~2'; END",
"CREATE PROCEDURE p1b() BEGIN CALL p2a(); END",
"CREATE PROCEDURE p2() BEGIN SELECT * FROM myhistorytable; END",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
"DELETE FROM myhistorytable;",
"INSERT INTO myhistorytable VALUES (1, 'first row, 2'), (2, 'second row, 2'), (3, 'third row, 2');",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
"DROP TABLE myhistorytable;",
"CREATE TABLE myhistorytable (pk BIGINT PRIMARY KEY, s TEXT, c TEXT);",
"INSERT INTO myhistorytable VALUES (1, 'first row, 3', '1'), (2, 'second row, 3', '2'), (3, 'third row, 3', '3');",
"CREATE PROCEDURE p2a() BEGIN SELECT * FROM myhistorytable AS OF 'HEAD~1'; END",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL p1();",
Expected: []sql.Row{
{int64(1), "first row, 3", "1"},
{int64(2), "second row, 3", "2"},
{int64(3), "third row, 3", "3"},
},
},
{
Query: "CALL p1a();",
Expected: []sql.Row{
{int64(1), "first row, 1"},
{int64(2), "second row, 1"},
{int64(3), "third row, 1"},
},
},
{
Query: "CALL p1b();",
Expected: []sql.Row{
{int64(1), "first row, 2"},
{int64(2), "second row, 2"},
{int64(3), "third row, 2"},
},
},
{
Query: "CALL p2();",
Expected: []sql.Row{
{int64(1), "first row, 3", "1"},
{int64(2), "second row, 3", "2"},
{int64(3), "third row, 3", "3"},
},
},
{
Query: "CALL p2a();",
Expected: []sql.Row{
{int64(1), "first row, 2"},
{int64(2), "second row, 2"},
{int64(3), "third row, 2"},
},
},
{
Query: "CALL p1() AS OF 'HEAD~2';",
Expected: []sql.Row{
{int64(1), "first row, 1"},
{int64(2), "second row, 1"},
{int64(3), "third row, 1"},
},
},
{
Query: "CALL p1a() AS OF 'HEAD';",
Expected: []sql.Row{
{int64(1), "first row, 1"},
{int64(2), "second row, 1"},
{int64(3), "third row, 1"},
},
},
{
Query: "CALL p1b() AS OF 'HEAD';",
Expected: []sql.Row{
{int64(1), "first row, 2"},
{int64(2), "second row, 2"},
{int64(3), "third row, 2"},
},
},
{
Query: "CALL p2() AS OF 'HEAD~2';",
Expected: []sql.Row{
{int64(1), "first row, 1"},
{int64(2), "second row, 1"},
{int64(3), "third row, 1"},
},
},
{
Query: "CALL p2a() AS OF 'HEAD';",
Expected: []sql.Row{
{int64(1), "first row, 2"},
{int64(2), "second row, 2"},
{int64(3), "third row, 2"},
},
},
},
},
{
Name: "CALL ... AS OF errors if attempting to modify a table",
SetUpScript: []string{
"CREATE TABLE test (v1 BIGINT);",
"INSERT INTO test VALUES (2);",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
`CREATE PROCEDURE p1()
BEGIN
UPDATE test SET v1 = v1 * 2;
END`,
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{2}},
},
{
Query: "CALL p1();",
Expected: []sql.Row{{sql.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{4}},
},
{
Query: "CALL p1() AS OF 'HEAD~1';",
ExpectedErr: sql.ErrProcedureCallAsOfReadOnly,
},
},
},
{
Name: "Database syntax propogates to inner calls",
SetUpScript: []string{
"CALL DOLT_CHECKOUT('main');",
`CREATE PROCEDURE p4()
BEGIN
CALL p5();
END`,
`CREATE PROCEDURE p5()
BEGIN
SELECT 3;
END`,
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
"CALL DOLT_BRANCH('p45');",
"DROP PROCEDURE p4;",
"DROP PROCEDURE p5;",
`CREATE PROCEDURE p4()
BEGIN
CALL p5();
END`,
`CREATE PROCEDURE p5()
BEGIN
SELECT 4;
END`,
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL p4();",
Expected: []sql.Row{{4}},
},
{
Query: "CALL p5();",
Expected: []sql.Row{{4}},
},
{
Query: "CALL `mydb/main`.p4();",
Expected: []sql.Row{{4}},
},
{
Query: "CALL `mydb/main`.p5();",
Expected: []sql.Row{{4}},
},
{
Query: "CALL `mydb/p45`.p4();",
Expected: []sql.Row{{3}},
},
{
Query: "CALL `mydb/p45`.p5();",
Expected: []sql.Row{{3}},
},
},
},
{
Name: "Database syntax with AS OF",
SetUpScript: []string{
"CREATE TABLE test (v1 BIGINT);",
"INSERT INTO test VALUES (2);",
`CREATE PROCEDURE p1()
BEGIN
SELECT v1 * 10 FROM test;
END`,
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
"CALL DOLT_BRANCH('other');",
"DROP PROCEDURE p1;",
`CREATE PROCEDURE p1()
BEGIN
SELECT v1 * 100 FROM test;
END`,
"UPDATE test SET v1 = 3;",
"CALL DOLT_ADD('-A');",
"CALL DOLT_COMMIT('-m', 'commit message');",
},
Assertions: []queries.ScriptTestAssertion{
{
Query: "CALL p1();",
Expected: []sql.Row{{300}},
},
{
Query: "CALL `mydb/main`.p1();",
Expected: []sql.Row{{300}},
},
{
Query: "CALL `mydb/other`.p1();",
Expected: []sql.Row{{30}},
},
{
Query: "CALL p1() AS OF 'HEAD';",
Expected: []sql.Row{{300}},
},
{
Query: "CALL `mydb/main`.p1() AS OF 'HEAD';",
Expected: []sql.Row{{300}},
},
{
Query: "CALL `mydb/other`.p1() AS OF 'HEAD';",
Expected: []sql.Row{{30}},
},
{
Query: "CALL p1() AS OF 'HEAD~1';",
Expected: []sql.Row{{200}},
},
{
Query: "CALL `mydb/main`.p1() AS OF 'HEAD~1';",
Expected: []sql.Row{{200}},
},
{
Query: "CALL `mydb/other`.p1() AS OF 'HEAD~1';",
Expected: []sql.Row{{20}},
},
},
},
}
@@ -107,7 +107,9 @@ func DoltProceduresGetTable(ctx *sql.Context, db Database) (*WritableDoltTable,
}
}
func DoltProceduresGetAll(ctx *sql.Context, db Database) ([]sql.StoredProcedureDetails, error) {
// DoltProceduresGetAll returns all stored procedures for the database if the procedureName is blank (and empty string),
// or it returns only the procedure with the matching name if one is given. The name is not case-sensitive.
func DoltProceduresGetAll(ctx *sql.Context, db Database, procedureName string) ([]sql.StoredProcedureDetails, error) {
tbl, err := DoltProceduresGetTable(ctx, db)
if err != nil {
return nil, err
@@ -129,7 +131,12 @@ func DoltProceduresGetAll(ctx *sql.Context, db Database) ([]sql.StoredProcedureD
}
nameExpr := idx.Expressions()[0]
lookup, err := sql.NewIndexBuilder(idx).IsNotNull(ctx, nameExpr).Build(ctx)
var lookup sql.IndexLookup
if procedureName == "" {
lookup, err = sql.NewIndexBuilder(idx).IsNotNull(ctx, nameExpr).Build(ctx)
} else {
lookup, err = sql.NewIndexBuilder(idx).Equals(ctx, nameExpr, procedureName).Build(ctx)
}
if err != nil {
return nil, err
}
@@ -25,9 +25,9 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/types"
)
@@ -248,7 +248,7 @@ func pullBranches(
}
_, err := rrd.limiter.Run(ctx, "-all", func() (any, error) {
err := rrd.ddb.PullChunks(ctx, rrd.tmpDir, rrd.srcDB, remoteHashes, nil, nil)
err := rrd.ddb.PullChunks(ctx, rrd.tmpDir, rrd.srcDB, remoteHashes, nil)
for _, remoteRef := range remoteRefs {
localRef, localRefExists := localRefsByPath[remoteRef.Ref.GetPath()]
@@ -305,41 +305,60 @@ func pullBranches(
// update the current working set if necessary
if remoteRef, ok := remoteRefsByPath[currentBranchRef.GetPath()]; ok {
cm, err := rrd.srcDB.ReadCommit(ctx, remoteRef.Hash)
wsRef, err := ref.WorkingSetRefForHead(currentBranchRef)
if err != nil {
return err
}
// Loop on optimistic lock failures.
for {
wsRef, err := ref.WorkingSetRefForHead(currentBranchRef)
if err != nil {
return err
}
ws, err := rrd.ddb.ResolveWorkingSet(ctx, wsRef)
if err != nil {
return err
}
prevHash, err := ws.HashOf()
if err != nil {
return err
}
wsWorkingRootHash, err := ws.WorkingRoot().HashOf()
if err != nil {
return err
}
wsStagedRootHash, err := ws.StagedRoot().HashOf()
if err != nil {
return err
}
ws, err := rrd.ddb.ResolveWorkingSet(ctx, wsRef)
if err != nil {
return err
}
// The branch heads could have moved since we pulled
// them. We re-resolve the upstream ref every time to
// ensure we don't go backwards if another thread moves
// our working set due to read replication.
cm, err := rrd.srcDB.ResolveCommitRef(ctx, remoteRef.Ref)
if err != nil {
return err
}
commitRoot, err := cm.GetRootValue(ctx)
if err != nil {
return err
}
commitRootHash, err := commitRoot.HashOf()
if err != nil {
return err
}
commitRoot, err := cm.GetRootValue(ctx)
if err != nil {
return err
}
if commitRootHash != wsWorkingRootHash || commitRootHash != wsStagedRootHash {
ws = ws.WithWorkingRoot(commitRoot).WithStagedRoot(commitRoot)
ws = ws.WithWorkingRoot(commitRoot).WithStagedRoot(commitRoot)
h, err := ws.HashOf()
if err != nil {
return err
err = rrd.ddb.UpdateWorkingSet(ctx, ws.Ref(), ws, prevHash, doltdb.TodoWorkingSetMeta())
if err == nil {
return nil
}
if !errors.Is(err, datas.ErrOptimisticLockFailed) {
return err
}
} else {
return nil
}
}
return rrd.ddb.UpdateWorkingSet(ctx, ws.Ref(), ws, h, doltdb.TodoWorkingSetMeta())
}
_, err = rrd.limiter.Run(ctx, "___tags", func() (any, error) {
tmpDir, err := rrd.rsw.TempTableFilesDir()
if err != nil {
return nil, err
}
// TODO: Not sure about this; see comment about the captured ctx below.
return nil, actions.FetchFollowTags(ctx, tmpDir, rrd.srcDB, rrd.ddb, actions.NoopRunProgFuncs, actions.NoopStopProgFuncs)
})
if err != nil {
return err
}
return nil
+1 -1
View File
@@ -815,7 +815,7 @@ func TestAlterSystemTables(t *testing.T) {
CreateTestTable(t, dEnv, doltdb.DoltQueryCatalogTableName, dtables.DoltQueryCatalogSchema,
"INSERT INTO dolt_query_catalog VALUES ('abc123', 1, 'example', 'select 2+2 from dual', 'description')")
CreateTestTable(t, dEnv, doltdb.SchemasTableName, SchemasTableSchema(),
"INSERT INTO dolt_schemas (type, name, fragment, id) VALUES ('view', 'name', 'select 2+2 from dual', 1)")
"INSERT INTO dolt_schemas (type, name, fragment, id) VALUES ('view', 'name', 'create view name as select 2+2 from dual', 1)")
}
t.Run("Create", func(t *testing.T) {
+2 -2
View File
@@ -199,7 +199,7 @@ var systemTableDeleteTests = []DeleteTest{
{
Name: "delete dolt_query_catalog",
AdditionalSetup: CreateTableFn(doltdb.DoltQueryCatalogTableName, dtables.DoltQueryCatalogSchema,
"INSERT INTO dolt_query_catalog VALUES ('abc123', 1, 'example', 'select 2+2 from dual', 'description')"),
"INSERT INTO dolt_query_catalog VALUES ('abc123', 1, 'example', 'create view example as select 2+2 from dual', 'description')"),
DeleteQuery: "delete from dolt_query_catalog",
SelectQuery: "select * from dolt_query_catalog",
ExpectedRows: ToSqlRows(dtables.DoltQueryCatalogSchema),
@@ -208,7 +208,7 @@ var systemTableDeleteTests = []DeleteTest{
{
Name: "delete dolt_schemas",
AdditionalSetup: CreateTableFn(doltdb.SchemasTableName, SchemasTableSchema(),
"INSERT INTO dolt_schemas (type, name, fragment, id) VALUES ('view', 'name', 'select 2+2 from dual', 1)"),
"INSERT INTO dolt_schemas (type, name, fragment, id) VALUES ('view', 'name', 'create view name as select 2+2 from dual', 1)"),
DeleteQuery: "delete from dolt_schemas",
SelectQuery: "select * from dolt_schemas",
ExpectedRows: ToSqlRows(dtables.DoltQueryCatalogSchema),
+18 -15
View File
@@ -272,30 +272,33 @@ func SqlRowAsCreateProcStmt(r sql.Row) (string, error) {
func SqlRowAsCreateFragStmt(r sql.Row) (string, error) {
var b strings.Builder
// Write create
b.WriteString("CREATE ")
// Write type
// If type is view, add DROP VIEW IF EXISTS statement before CREATE VIEW STATEMENT
typeStr := strings.ToUpper(r[0].(string))
b.WriteString(typeStr)
b.WriteString(" ") // add a space
// Write view/trigger name
nameStr := r[1].(string)
b.WriteString(QuoteIdentifier(nameStr))
b.WriteString(" ") // add a space
if typeStr == "VIEW" {
nameStr := r[1].(string)
dropStmt := fmt.Sprintf("DROP VIEW IF EXISTS `%s`", nameStr)
b.WriteString(dropStmt)
b.WriteString(";\n")
}
// Parse statement to extract definition (and remove any weird whitespace issues)
defStmt, err := sqlparser.Parse(r[2].(string))
if err != nil {
return "", err
}
defStr := sqlparser.String(defStmt)
if typeStr == "TRIGGER" { // triggers need the create trigger <trig_name> to be cut off
defStr = defStr[len("CREATE TRIGGER ")+len(nameStr)+1:]
} else { // views need the prefixed with "AS"
defStr = "AS " + defStr
// TODO: this is temporary fix for create statements
if typeStr == "TRIGGER" {
nameStr := r[1].(string)
defStr = fmt.Sprintf("CREATE TRIGGER `%s` %s", nameStr, defStr[len("CREATE TRIGGER ")+len(nameStr)+1:])
} else {
defStr = strings.Replace(defStr, "create ", "CREATE ", -1)
defStr = strings.Replace(defStr, " view ", " VIEW ", -1)
defStr = strings.Replace(defStr, " as ", " AS ", -1)
}
b.WriteString(defStr)
b.WriteString(";")
+2 -2
View File
@@ -398,10 +398,10 @@ var systemTableInsertTests = []InsertTest{
{
Name: "insert into dolt_schemas",
AdditionalSetup: CreateTableFn(doltdb.SchemasTableName, SchemasTableSchema(), ""),
InsertQuery: "insert into dolt_schemas (id, type, name, fragment) values (1, 'view', 'name', 'select 2+2 from dual')",
InsertQuery: "insert into dolt_schemas (id, type, name, fragment) values (1, 'view', 'name', 'create view name as select 2+2 from dual')",
SelectQuery: "select * from dolt_schemas ORDER BY id",
ExpectedRows: ToSqlRows(CompressSchema(SchemasTableSchema()),
NewRow(types.String("view"), types.String("name"), types.String("select 2+2 from dual"), types.Int(1)),
NewRow(types.String("view"), types.String("name"), types.String("create view name as select 2+2 from dual"), types.Int(1)),
),
ExpectedSchema: CompressSchema(SchemasTableSchema()),
},
@@ -273,10 +273,10 @@ var systemTableReplaceTests = []ReplaceTest{
{
Name: "replace into dolt_schemas",
AdditionalSetup: CreateTableFn(doltdb.SchemasTableName, SchemasTableSchema(),
"INSERT INTO dolt_schemas VALUES ('view', 'name', 'select 2+2 from dual', 1, NULL)"),
ReplaceQuery: "replace into dolt_schemas (id, type, name, fragment) values ('1', 'view', 'name', 'select 1+1 from dual')",
"INSERT INTO dolt_schemas VALUES ('view', 'name', 'create view name as select 2+2 from dual', 1, NULL)"),
ReplaceQuery: "replace into dolt_schemas (id, type, name, fragment) values ('1', 'view', 'name', 'create view name as select 1+1 from dual')",
SelectQuery: "select type, name, fragment, id, extra from dolt_schemas",
ExpectedRows: []sql.Row{{"view", "name", "select 1+1 from dual", int64(1), nil}},
ExpectedRows: []sql.Row{{"view", "name", "create view name as select 1+1 from dual", int64(1), nil}},
ExpectedSchema: CompressSchema(SchemasTableSchema()),
},
}
+2 -2
View File
@@ -1310,9 +1310,9 @@ var systemTableSelectTests = []SelectTest{
{
Name: "select from dolt_schemas",
AdditionalSetup: CreateTableFn(doltdb.SchemasTableName, SchemasTableSchema(),
`INSERT INTO dolt_schemas VALUES ('view', 'name', 'select 2+2 from dual', 1, NULL)`),
`INSERT INTO dolt_schemas VALUES ('view', 'name', 'create view name as select 2+2 from dual', 1, NULL)`),
Query: "select * from dolt_schemas",
ExpectedRows: []sql.Row{{"view", "name", "select 2+2 from dual", int64(1), nil}},
ExpectedRows: []sql.Row{{"view", "name", "create view name as select 2+2 from dual", int64(1), nil}},
ExpectedSchema: CompressSchema(SchemasTableSchema()),
},
}
+2 -2
View File
@@ -378,10 +378,10 @@ var systemTableUpdateTests = []UpdateTest{
{
Name: "update dolt_schemas",
AdditionalSetup: CreateTableFn(doltdb.SchemasTableName, SchemasTableSchema(),
`INSERT INTO dolt_schemas VALUES ('view', 'name', 'select 2+2 from dual', 1, NULL)`),
`INSERT INTO dolt_schemas VALUES ('view', 'name', 'create view name as select 2+2 from dual', 1, NULL)`),
UpdateQuery: "update dolt_schemas set type = 'not a view'",
SelectQuery: "select * from dolt_schemas",
ExpectedRows: []sql.Row{{"not a view", "name", "select 2+2 from dual", int64(1), nil}},
ExpectedRows: []sql.Row{{"not a view", "name", "create view name as select 2+2 from dual", int64(1), nil}},
ExpectedSchema: CompressSchema(SchemasTableSchema()),
},
}
+5 -9
View File
@@ -292,10 +292,10 @@ func (t *DoltTable) HasIndex(ctx *sql.Context, idx sql.Index) (bool, error) {
}
// GetAutoIncrementValue gets the last AUTO_INCREMENT value
func (t *DoltTable) GetAutoIncrementValue(ctx *sql.Context) (interface{}, error) {
func (t *DoltTable) GetAutoIncrementValue(ctx *sql.Context) (uint64, error) {
table, err := t.DoltTable(ctx)
if err != nil {
return nil, err
return 0, err
}
return table.GetAutoIncrementValue(ctx)
}
@@ -747,12 +747,12 @@ func (t *WritableDoltTable) AutoIncrementSetter(ctx *sql.Context) sql.AutoIncrem
}
// PeekNextAutoIncrementValue implements sql.AutoIncrementTable
func (t *WritableDoltTable) PeekNextAutoIncrementValue(ctx *sql.Context) (interface{}, error) {
func (t *WritableDoltTable) PeekNextAutoIncrementValue(ctx *sql.Context) (uint64, error) {
if !t.autoIncCol.AutoIncrement {
return nil, sql.ErrNoAutoIncrementCol
return 0, sql.ErrNoAutoIncrementCol
}
return t.getTableAutoIncrementValue(ctx)
return t.DoltTable.GetAutoIncrementValue(ctx)
}
// GetNextAutoIncrementValue implements sql.AutoIncrementTable
@@ -769,10 +769,6 @@ func (t *WritableDoltTable) GetNextAutoIncrementValue(ctx *sql.Context, potentia
return ed.GetNextAutoIncrementValue(ctx, potentialVal)
}
func (t *WritableDoltTable) getTableAutoIncrementValue(ctx *sql.Context) (interface{}, error) {
return t.DoltTable.GetAutoIncrementValue(ctx)
}
func (t *DoltTable) GetChecks(ctx *sql.Context) ([]sql.CheckDefinition, error) {
table, err := t.DoltTable(ctx)
if err != nil {
+8 -4
View File
@@ -8,8 +8,10 @@ tests:
scripts:
- gen/dolt_commit_ancestors_commit_filter.gen.lua
- gen/dolt_commits_commit_filter.gen.lua
- gen/dolt_diff_log_join_on_commit.gen.lua
- gen/dolt_diff_table_commit_filter.gen.lua
- gen/dolt_diff_log_join_to_commit.gen.lua
- gen/dolt_diff_table_to_commit_filter.gen.lua
- gen/dolt_diff_log_join_from_commit.gen.lua
- gen/dolt_diff_table_from_commit_filter.gen.lua
- gen/dolt_diffs_commit_filter.gen.lua
- gen/dolt_history_commit_filter.gen.lua
- gen/dolt_log_commit_filter.gen.lua
@@ -22,8 +24,10 @@ tests:
scripts:
- gen/dolt_commit_ancestors_commit_filter_dummy.gen.lua
- gen/dolt_commits_commit_filter_dummy.gen.lua
- gen/dolt_diff_log_join_on_commit_dummy.gen.lua
- gen/dolt_diff_table_commit_filter_dummy.gen.lua
- gen/dolt_diff_log_join_to_commit_dummy.gen.lua
- gen/dolt_diff_table_to_commit_filter_dummy.gen.lua
- gen/dolt_diff_log_join_from_commit_dummy.gen.lua
- gen/dolt_diff_table_from_commit_filter_dummy.gen.lua
- gen/dolt_diffs_commit_filter_dummy.gen.lua
- gen/dolt_history_commit_filter_dummy.gen.lua
- gen/dolt_log_commit_filter_dummy.gen.lua
+3
View File
@@ -22,6 +22,9 @@ import (
// Blobstore is an interface for storing and retrieving blobs of data by key
type Blobstore interface {
// Path returns this blobstore's path.
Path() (path string)
// Exists returns true if a blob keyed by |key| exists.
Exists(ctx context.Context, key string) (ok bool, err error)
+1 -1
View File
@@ -88,7 +88,7 @@ func appendLocalTest(tests []BlobstoreTest) []BlobstoreTest {
func newBlobStoreTests() []BlobstoreTest {
var tests []BlobstoreTest
tests = append(tests, BlobstoreTest{"inmem", NewInMemoryBlobstore(), 10, 20})
tests = append(tests, BlobstoreTest{"inmem", NewInMemoryBlobstore(""), 10, 20})
tests = appendLocalTest(tests)
tests = appendGCSTest(tests)
+4 -1
View File
@@ -42,7 +42,6 @@ type GCSBlobstore struct {
var _ Blobstore = &GCSBlobstore{}
// NewGCSBlobstore creates a new instance of a GCSBlobstare
func NewGCSBlobstore(gcs *storage.Client, bucketName, prefix string) *GCSBlobstore {
for len(prefix) > 0 && prefix[0] == '/' {
prefix = prefix[1:]
@@ -52,6 +51,10 @@ func NewGCSBlobstore(gcs *storage.Client, bucketName, prefix string) *GCSBlobsto
return &GCSBlobstore{bucket, bucketName, prefix}
}
func (bs *GCSBlobstore) Path() string {
return path.Join(bs.bucketName, bs.prefix)
}
// Exists returns true if a blob exists for the given key, and false if it does not.
// For InMemoryBlobstore instances error should never be returned (though other
// implementations of this interface can)
+11 -2
View File
@@ -38,6 +38,7 @@ func newByteSliceReadCloser(data []byte) *byteSliceReadCloser {
// InMemoryBlobstore provides an in memory implementation of the Blobstore interface
type InMemoryBlobstore struct {
path string
mutex sync.RWMutex
blobs map[string][]byte
versions map[string]string
@@ -46,8 +47,16 @@ type InMemoryBlobstore struct {
var _ Blobstore = &InMemoryBlobstore{}
// NewInMemoryBlobstore creates an instance of an InMemoryBlobstore
func NewInMemoryBlobstore() *InMemoryBlobstore {
return &InMemoryBlobstore{blobs: make(map[string][]byte), versions: make(map[string]string)}
func NewInMemoryBlobstore(path string) *InMemoryBlobstore {
return &InMemoryBlobstore{
path: path,
blobs: make(map[string][]byte),
versions: make(map[string]string),
}
}
func (bs *InMemoryBlobstore) Path() string {
return bs.path
}
// Get retrieves an io.reader for the portion of a blob specified by br along with
+4
View File
@@ -75,6 +75,10 @@ func NewLocalBlobstore(dir string) *LocalBlobstore {
return &LocalBlobstore{dir}
}
func (bs *LocalBlobstore) Path() string {
return bs.RootDir
}
// Get retrieves an io.reader for the portion of a blob specified by br along with
// its version
func (bs *LocalBlobstore) Get(ctx context.Context, key string, br BlobRange) (io.ReadCloser, string, error) {
+4
View File
@@ -59,6 +59,10 @@ func NewOSSBlobstore(ossClient *oss.Client, bucketName, prefix string) (*OSSBlob
}, nil
}
func (ob *OSSBlobstore) Path() string {
return path.Join(ob.bucketName, ob.prefix)
}
func (ob *OSSBlobstore) Exists(_ context.Context, key string) (bool, error) {
return ob.bucket.IsObjectExist(ob.absKey(key))
}
+5 -2
View File
@@ -31,6 +31,8 @@ import (
var ErrNothingToCollect = errors.New("no changes since last gc")
type GetAddrsCb func(ctx context.Context, c Chunk) (hash.HashSet, error)
// ChunkStore is the core storage abstraction in noms. We can put data
// anyplace we have a ChunkStore implementation for.
type ChunkStore interface {
@@ -54,8 +56,9 @@ type ChunkStore interface {
// Put caches c in the ChunkSource. Upon return, c must be visible to
// subsequent Get and Has calls, but must not be persistent until a call
// to Flush(). Put may be called concurrently with other calls to Put(),
// Get(), GetMany(), Has() and HasMany().
Put(ctx context.Context, c Chunk) error
// Get(), GetMany(), Has() and HasMany(). Will return an error if the
// addrs returned by `getAddrs` are absent from the chunk store.
Put(ctx context.Context, c Chunk, getAddrs GetAddrsCb) error
// Returns the NomsVersion with which this ChunkSource is compatible.
Version() string
+16 -3
View File
@@ -36,16 +36,29 @@ type ChunkStoreTestSuite struct {
Factory *memoryStoreFactory
}
func getAddrsCb(ctx context.Context, c Chunk) (hash.HashSet, error) {
return nil, nil
}
func (suite *ChunkStoreTestSuite) TestChunkStorePut() {
store := suite.Factory.CreateStore(context.Background(), "ns")
input := "abc"
c := NewChunk([]byte(input))
err := store.Put(context.Background(), c)
err := store.Put(context.Background(), c, getAddrsCb)
suite.NoError(err)
h := c.Hash()
// Reading it via the API should work.
assertInputInStore(input, h, store, suite.Assert())
// Put chunk with dangling ref should error
data := []byte("bcd")
r := hash.Of(data)
nc := NewChunk(data)
err = store.Put(context.Background(), nc, func(ctx context.Context, c Chunk) (hash.HashSet, error) {
return hash.NewHashSet(r), nil
})
suite.Error(err)
}
func (suite *ChunkStoreTestSuite) TestChunkStoreRoot() {
@@ -73,7 +86,7 @@ func (suite *ChunkStoreTestSuite) TestChunkStoreCommitPut() {
store := suite.Factory.CreateStore(context.Background(), name)
input := "abc"
c := NewChunk([]byte(input))
err := store.Put(context.Background(), c)
err := store.Put(context.Background(), c, getAddrsCb)
suite.NoError(err)
h := c.Hash()
@@ -115,7 +128,7 @@ func (suite *ChunkStoreTestSuite) TestChunkStoreCommitUnchangedRoot() {
store1, store2 := suite.Factory.CreateStore(context.Background(), "ns"), suite.Factory.CreateStore(context.Background(), "ns")
input := "abc"
c := NewChunk([]byte(input))
err := store1.Put(context.Background(), c)
err := store1.Put(context.Background(), c, getAddrsCb)
suite.NoError(err)
h := c.Hash()
+2 -2
View File
@@ -100,9 +100,9 @@ func (csMW *CSMetricWrapper) HasMany(ctx context.Context, hashes hash.HashSet) (
// subsequent Get and Has calls, but must not be persistent until a call
// to Flush(). Put may be called concurrently with other calls to Put(),
// Get(), GetMany(), Has() and HasMany().
func (csMW *CSMetricWrapper) Put(ctx context.Context, c Chunk) error {
func (csMW *CSMetricWrapper) Put(ctx context.Context, c Chunk, getAddrs GetAddrsCb) error {
atomic.AddInt32(&csMW.TotalChunkPuts, 1)
return csMW.cs.Put(ctx, c)
return csMW.cs.Put(ctx, c, getAddrs)
}
// Returns the NomsVersion with which this ChunkSource is compatible.
+23 -1
View File
@@ -186,7 +186,29 @@ func (ms *MemoryStoreView) Version() string {
return ms.version
}
func (ms *MemoryStoreView) Put(ctx context.Context, c Chunk) error {
func (ms *MemoryStoreView) errorIfDangling(ctx context.Context, addrs hash.HashSet) error {
absent, err := ms.HasMany(ctx, addrs)
if err != nil {
return err
}
if len(absent) != 0 {
s := absent.String()
return fmt.Errorf("Found dangling references to %s", s)
}
return nil
}
func (ms *MemoryStoreView) Put(ctx context.Context, c Chunk, getAddrs GetAddrsCb) error {
addrs, err := getAddrs(ctx, c)
if err != nil {
return err
}
err = ms.errorIfDangling(ctx, addrs)
if err != nil {
return err
}
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.pending == nil {
+2 -2
View File
@@ -66,9 +66,9 @@ func (s *TestStoreView) HasMany(ctx context.Context, hashes hash.HashSet) (hash.
return s.ChunkStore.HasMany(ctx, hashes)
}
func (s *TestStoreView) Put(ctx context.Context, c Chunk) error {
func (s *TestStoreView) Put(ctx context.Context, c Chunk, getAddrs GetAddrsCb) error {
atomic.AddInt32(&s.writes, 1)
return s.ChunkStore.Put(ctx, c)
return s.ChunkStore.Put(ctx, c, getAddrs)
}
func (s *TestStoreView) MarkAndSweepChunks(ctx context.Context, last hash.Hash, keepChunks <-chan []hash.Hash, dest ChunkStore) error {
-9
View File
@@ -44,7 +44,6 @@ var commands = []*util.Command{
nomsDs,
nomsRoot,
nomsShow,
nomsSync,
nomsVersion,
nomsManifest,
nomsCat,
@@ -213,14 +212,6 @@ See Spelling Objects at https://github.com/attic-labs/noms/blob/master/doc/spell
show.Flag("tz", "display formatted date comments in specified timezone, must be: local or utc").Enum("local", "utc")
show.Arg("object", "a noms object").Required().String()
// sync
sync := noms.Command("sync", `Moves datasets between or within databases
See Spelling Objects at https://github.com/attic-labs/noms/blob/master/doc/spelling.md for details on the object and dataset arguments.
`)
sync.Flag("parallelism", "").Short('p').Default("512").Int()
sync.Arg("source-object", "a noms source object").Required().String()
sync.Arg("dest-dataset", "a noms dataset").Required().String()
// version
noms.Command("version", "Print the noms version")
-160
View File
@@ -1,160 +0,0 @@
// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// 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 (
"context"
"fmt"
"log"
"time"
"github.com/dustin/go-humanize"
flag "github.com/juju/gnuflag"
"github.com/dolthub/dolt/go/store/cmd/noms/util"
"github.com/dolthub/dolt/go/store/config"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/datas/pull"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/types"
"github.com/dolthub/dolt/go/store/util/profile"
"github.com/dolthub/dolt/go/store/util/status"
"github.com/dolthub/dolt/go/store/util/verbose"
)
var (
p int
)
var nomsSync = &util.Command{
Run: runSync,
UsageLine: "sync [options] <source-object> <dest-dataset>",
Short: "Moves datasets between or within databases",
Long: "See Spelling Objects at https://github.com/attic-labs/noms/blob/master/doc/spelling.md for details on the object and dataset arguments.",
Flags: setupSyncFlags,
Nargs: 2,
}
func setupSyncFlags() *flag.FlagSet {
syncFlagSet := flag.NewFlagSet("sync", flag.ExitOnError)
syncFlagSet.IntVar(&p, "p", 512, "parallelism")
verbose.RegisterVerboseFlags(syncFlagSet)
profile.RegisterProfileFlags(syncFlagSet)
return syncFlagSet
}
func runSync(ctx context.Context, args []string) int {
cfg := config.NewResolver()
sourceStore, sourceVRW, sourceObj, err := cfg.GetPath(ctx, args[0])
util.CheckError(err)
defer sourceStore.Close()
if sourceObj == nil {
util.CheckErrorNoUsage(fmt.Errorf("Object not found: %s", args[0]))
}
sinkDB, _, sinkDataset, err := cfg.GetDataset(ctx, args[1])
util.CheckError(err)
defer sinkDB.Close()
start := time.Now()
progressCh := make(chan pull.PullProgress)
lastProgressCh := make(chan pull.PullProgress)
go func() {
var last pull.PullProgress
for info := range progressCh {
last = info
if info.KnownCount == 1 {
// It's better to print "up to date" than "0% (0/1); 100% (1/1)".
continue
}
if status.WillPrint() {
pct := 100.0 * float64(info.DoneCount) / float64(info.KnownCount)
status.Printf("Syncing - %.2f%% (%s/s)", pct, bytesPerSec(info.ApproxWrittenBytes, start))
}
}
lastProgressCh <- last
}()
sourceRef, err := types.NewRef(sourceObj, sourceVRW.Format())
util.CheckError(err)
sinkAddr, sinkExists := sinkDataset.MaybeHeadAddr()
nonFF := false
srcCS := datas.ChunkStoreFromDatabase(sourceStore)
sinkCS := datas.ChunkStoreFromDatabase(sinkDB)
waf := types.WalkAddrsForNBF(sourceVRW.Format())
f := func() error {
defer profile.MaybeStartProfile().Stop()
addr := sourceRef.TargetHash()
err := pull.Pull(ctx, srcCS, sinkCS, waf, []hash.Hash{addr}, progressCh)
if err != nil {
return err
}
var tempDS datas.Dataset
tempDS, err = sinkDB.FastForward(ctx, sinkDataset, sourceRef.TargetHash())
if err == datas.ErrMergeNeeded {
sinkDataset, err = sinkDB.SetHead(ctx, sinkDataset, addr)
nonFF = true
} else if err == nil {
sinkDataset = tempDS
}
return err
}
err = f()
if err != nil {
log.Fatal(err)
}
close(progressCh)
if last := <-lastProgressCh; last.DoneCount > 0 {
status.Printf("Done - Synced %s in %s (%s/s)",
humanize.Bytes(last.ApproxWrittenBytes), since(start), bytesPerSec(last.ApproxWrittenBytes, start))
status.Done()
} else if !sinkExists {
fmt.Printf("All chunks already exist at destination! Created new dataset %s.\n", args[1])
} else if nonFF && sourceRef.TargetHash() != sinkAddr {
fmt.Printf("Abandoning %s; new head is %s\n", sinkAddr, sourceRef.TargetHash())
} else {
fmt.Printf("Dataset %s is already up to date.\n", args[1])
}
return 0
}
func bytesPerSec(bytes uint64, start time.Time) string {
bps := float64(bytes) / float64(time.Since(start).Seconds())
return humanize.Bytes(uint64(bps))
}
func since(start time.Time) string {
round := time.Second / 100
now := time.Now().Round(round)
return now.Sub(start.Round(round)).String()
}
-229
View File
@@ -1,229 +0,0 @@
// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// 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 (
"context"
"testing"
"github.com/stretchr/testify/suite"
"github.com/dolthub/dolt/go/libraries/utils/file"
"github.com/dolthub/dolt/go/store/d"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/nbs"
"github.com/dolthub/dolt/go/store/spec"
"github.com/dolthub/dolt/go/store/types"
"github.com/dolthub/dolt/go/store/util/clienttest"
)
func TestSync(t *testing.T) {
suite.Run(t, &nomsSyncTestSuite{})
}
type nomsSyncTestSuite struct {
clienttest.ClientTestSuite
}
func (s *nomsSyncTestSuite) TestSyncValidation() {
cs, err := nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
sourceDB := datas.NewDatabase(cs)
source1, err := sourceDB.GetDataset(context.Background(), "src")
s.NoError(err)
source1, err = datas.CommitValue(context.Background(), sourceDB, source1, types.Float(42))
s.NoError(err)
ref, ok, err := source1.MaybeHeadRef()
s.NoError(err)
s.True(ok)
source1HeadRef := ref.TargetHash()
source1.Database().Close()
sourceSpecMissingHashSymbol := spec.CreateValueSpecString("nbs", s.DBDir, source1HeadRef.String())
sinkDatasetSpec := spec.CreateValueSpecString("nbs", s.DBDir2, "dest")
defer func() {
err := recover()
s.Equal(clienttest.ExitError{Code: 1}, err)
}()
s.MustRun(main, []string{"sync", sourceSpecMissingHashSymbol, sinkDatasetSpec})
}
func (s *nomsSyncTestSuite) TestSync() {
defer s.NoError(file.RemoveAll(s.DBDir2))
cs, err := nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
sourceDB := datas.NewDatabase(cs)
source1, err := sourceDB.GetDataset(context.Background(), "src")
s.NoError(err)
source1, err = datas.CommitValue(context.Background(), sourceDB, source1, types.Float(42))
s.NoError(err)
ref, ok, err := source1.MaybeHeadRef()
s.NoError(err)
s.True(ok)
source1HeadRef := ref.TargetHash()
s.NoError(err)
source1, err = datas.CommitValue(context.Background(), sourceDB, source1, types.Float(43))
s.NoError(err)
sourceDB.Close()
// Pull from a hash to a not-yet-existing dataset in a new DB
sourceSpec := spec.CreateValueSpecString("nbs", s.DBDir, "#"+source1HeadRef.String())
sinkDatasetSpec := spec.CreateValueSpecString("nbs", s.DBDir2, "dest")
sout, _ := s.MustRun(main, []string{"sync", sourceSpec, sinkDatasetSpec})
s.Regexp("Synced", sout)
cs, err = nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir2, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
db := datas.NewDatabase(cs)
dest, err := db.GetDataset(context.Background(), "dest")
s.NoError(err)
s.True(types.Float(42).Equals(mustHeadValue(dest)))
db.Close()
// Pull from a dataset in one DB to an existing dataset in another
sourceDataset := spec.CreateValueSpecString("nbs", s.DBDir, "src")
sout, _ = s.MustRun(main, []string{"sync", sourceDataset, sinkDatasetSpec})
s.Regexp("Synced", sout)
cs, err = nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir2, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
db = datas.NewDatabase(cs)
dest, err = db.GetDataset(context.Background(), "dest")
s.NoError(err)
s.True(types.Float(43).Equals(mustHeadValue(dest)))
db.Close()
// Pull when sink dataset is already up to date
sout, _ = s.MustRun(main, []string{"sync", sourceDataset, sinkDatasetSpec})
s.Regexp("up to date", sout)
// Pull from a source dataset to a not-yet-existing dataset in another DB, BUT all the needed chunks already exists in the sink.
sinkDatasetSpec = spec.CreateValueSpecString("nbs", s.DBDir2, "dest2")
sout, _ = s.MustRun(main, []string{"sync", sourceDataset, sinkDatasetSpec})
s.Regexp("Created", sout)
cs, err = nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir2, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
db = datas.NewDatabase(cs)
dest, err = db.GetDataset(context.Background(), "dest2")
s.NoError(err)
s.True(types.Float(43).Equals(mustHeadValue(dest)))
db.Close()
}
func (s *nomsSyncTestSuite) TestSync_Issue2598() {
defer s.NoError(file.RemoveAll(s.DBDir2))
cs, err := nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
sourceDB := datas.NewDatabase(cs)
// Create dataset "src1", which has a lineage of two commits.
source1, err := sourceDB.GetDataset(context.Background(), "src1")
s.NoError(err)
source1, err = datas.CommitValue(context.Background(), sourceDB, source1, types.Float(42))
s.NoError(err)
source1, err = datas.CommitValue(context.Background(), sourceDB, source1, types.Float(43))
s.NoError(err)
// Create dataset "src2", with a lineage of one commit.
source2, err := sourceDB.GetDataset(context.Background(), "src2")
s.NoError(err)
source2, err = datas.CommitValue(context.Background(), sourceDB, source2, types.Float(1))
s.NoError(err)
sourceDB.Close() // Close Database backing both Datasets
// Sync over "src1"
sourceDataset := spec.CreateValueSpecString("nbs", s.DBDir, "src1")
sinkDatasetSpec := spec.CreateValueSpecString("nbs", s.DBDir2, "dest")
sout, _ := s.MustRun(main, []string{"sync", sourceDataset, sinkDatasetSpec})
cs, err = nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir2, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
db := datas.NewDatabase(cs)
dest, err := db.GetDataset(context.Background(), "dest")
s.NoError(err)
s.True(types.Float(43).Equals(mustHeadValue(dest)))
db.Close()
// Now, try syncing a second dataset. This crashed in issue #2598
sourceDataset2 := spec.CreateValueSpecString("nbs", s.DBDir, "src2")
sinkDatasetSpec2 := spec.CreateValueSpecString("nbs", s.DBDir2, "dest2")
sout, _ = s.MustRun(main, []string{"sync", sourceDataset2, sinkDatasetSpec2})
cs, err = nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir2, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
db = datas.NewDatabase(cs)
dest, err = db.GetDataset(context.Background(), "dest2")
s.NoError(err)
s.True(types.Float(1).Equals(mustHeadValue(dest)))
db.Close()
sout, _ = s.MustRun(main, []string{"sync", sourceDataset, sinkDatasetSpec})
s.Regexp("up to date", sout)
}
func (s *nomsSyncTestSuite) TestRewind() {
var err error
cs, err := nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
sourceDB := datas.NewDatabase(cs)
src, err := sourceDB.GetDataset(context.Background(), "foo")
s.NoError(err)
src, err = datas.CommitValue(context.Background(), sourceDB, src, types.Float(42))
s.NoError(err)
rewindRef := mustHeadAddr(src)
src, err = datas.CommitValue(context.Background(), sourceDB, src, types.Float(43))
s.NoError(err)
sourceDB.Close() // Close Database backing both Datasets
sourceSpec := spec.CreateValueSpecString("nbs", s.DBDir, "#"+rewindRef.String())
sinkDatasetSpec := spec.CreateValueSpecString("nbs", s.DBDir, "foo")
s.MustRun(main, []string{"sync", sourceSpec, sinkDatasetSpec})
cs, err = nbs.NewLocalStore(context.Background(), types.Format_Default.VersionString(), s.DBDir, clienttest.DefaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider())
s.NoError(err)
db := datas.NewDatabase(cs)
dest, err := db.GetDataset(context.Background(), "foo")
s.NoError(err)
s.True(types.Float(42).Equals(mustHeadValue(dest)))
db.Close()
}
func mustHeadValue(ds datas.Dataset) types.Value {
val, ok, err := ds.MaybeHeadValue()
d.PanicIfError(err)
if !ok {
panic("no head")
}
return val
}
func mustHeadAddr(ds datas.Dataset) hash.Hash {
addr, ok := ds.MaybeHeadAddr()
d.PanicIfFalse(ok)
return addr
}
+4 -4
View File
@@ -94,7 +94,8 @@ func (suite *RemoteDatabaseSuite) TestWriteRefToNonexistentValue() {
suite.NoError(err)
r, err := types.NewRef(types.Bool(true), suite.db.Format())
suite.NoError(err)
suite.Panics(func() { CommitValue(context.Background(), suite.db, ds, r) })
_, err = CommitValue(context.Background(), suite.db, ds, r)
suite.Error(err)
}
func (suite *DatabaseSuite) TestTolerateUngettableRefs() {
@@ -127,9 +128,8 @@ func (suite *DatabaseSuite) TestCompletenessCheck() {
suite.NoError(err)
s, err = se.Set(context.Background()) // danging ref
suite.NoError(err)
suite.Panics(func() {
ds1, err = CommitValue(context.Background(), suite.db, ds1, s)
})
_, err = CommitValue(context.Background(), suite.db, ds1, s)
suite.Error(err)
}
func (suite *DatabaseSuite) TestRebase() {
+2
View File
@@ -29,6 +29,8 @@ import (
"github.com/dolthub/dolt/go/store/nbs"
)
var ErrNoData = errors.New("no data")
func Clone(ctx context.Context, srcCS, sinkCS chunks.ChunkStore, eventCh chan<- TableFileEvent) error {
srcTS, srcOK := srcCS.(nbs.TableFileStore)
-235
View File
@@ -1,235 +0,0 @@
// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// 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 pull
import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"sync"
"github.com/golang/snappy"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/dolthub/dolt/go/store/hash"
)
type PullProgress struct {
DoneCount, KnownCount, ApproxWrittenBytes uint64
}
const (
bytesWrittenSampleRate = .10
defaultBatchSize = 1 << 12 // 4096 chunks
)
var ErrNoData = errors.New("no data")
func makeProgTrack(progressCh chan PullProgress) func(moreDone, moreKnown, moreApproxBytesWritten uint64) {
var doneCount, knownCount, approxBytesWritten uint64
return func(moreDone, moreKnown, moreApproxBytesWritten uint64) {
if progressCh == nil {
return
}
doneCount, knownCount, approxBytesWritten = doneCount+moreDone, knownCount+moreKnown, approxBytesWritten+moreApproxBytesWritten
progressCh <- PullProgress{doneCount, knownCount, approxBytesWritten}
}
}
// Pull objects that descend from sourceHash from srcDB to sinkDB.
func Pull(ctx context.Context, srcCS, sinkCS chunks.ChunkStore, walkAddrs WalkAddrs, hashes []hash.Hash, progressCh chan PullProgress) error {
return pull(ctx, srcCS, sinkCS, walkAddrs, hashes, progressCh, defaultBatchSize)
}
func pull(ctx context.Context, srcCS, sinkCS chunks.ChunkStore, walkAddrs WalkAddrs, hashes []hash.Hash, progressCh chan PullProgress, batchSize int) error {
// Sanity Check
hs := hash.NewHashSet(hashes...)
missing, err := srcCS.HasMany(ctx, hs)
if err != nil {
return err
}
if missing.Size() != 0 {
return errors.New("not found")
}
hs = hash.NewHashSet(hashes...)
missing, err = sinkCS.HasMany(ctx, hs)
if err != nil {
return err
}
if missing.Size() == 0 {
return nil // already up to date
}
if srcCS.Version() != sinkCS.Version() {
return fmt.Errorf("cannot pull from src to sink; src version is %v and sink version is %v", srcCS.Version(), sinkCS.Version())
}
var sampleSize, sampleCount uint64
updateProgress := makeProgTrack(progressCh)
// TODO: This batches based on limiting the _number_ of chunks processed at the same time. We really want to batch based on the _amount_ of chunk data being processed simultaneously. We also want to consider the chunks in a particular order, however, and the current GetMany() interface doesn't provide any ordering guarantees. Once BUG 3750 is fixed, we should be able to revisit this and do a better job.
absent := make([]hash.Hash, len(hashes))
copy(absent, hashes)
for absentCount := len(absent); absentCount != 0; absentCount = len(absent) {
updateProgress(0, uint64(absentCount), 0)
// For gathering up the hashes in the next level of the tree
nextLevel := hash.HashSet{}
uniqueOrdered := hash.HashSlice{}
// Process all absent chunks in this level of the tree in quanta of at most |batchSize|
for start, end := 0, batchSize; start < absentCount; start, end = end, end+batchSize {
if end > absentCount {
end = absentCount
}
batch := absent[start:end]
neededChunks, err := getChunks(ctx, srcCS, batch, sampleSize, sampleCount, updateProgress)
if err != nil {
return err
}
uniqueOrdered, err = putChunks(ctx, walkAddrs, sinkCS, batch, neededChunks, nextLevel, uniqueOrdered)
if err != nil {
return err
}
}
absent, err = nextLevelMissingChunks(ctx, sinkCS, nextLevel, absent, uniqueOrdered)
if err != nil {
return err
}
}
err = persistChunks(ctx, sinkCS)
if err != nil {
return err
}
return nil
}
func persistChunks(ctx context.Context, cs chunks.ChunkStore) error {
// todo: there is no call to rebase on an unsuccessful Commit()
// will this loop forever?
var success bool
for !success {
r, err := cs.Root(ctx)
if err != nil {
return err
}
success, err = cs.Commit(ctx, r, r)
if err != nil {
return err
}
}
return nil
}
// PullWithoutBatching effectively removes the batching of chunk retrieval done on each level of the tree. This means
// all chunks from one level of the tree will be retrieved from the underlying chunk store in one call, which pushes the
// optimization problem down to the chunk store which can make smarter decisions.
func PullWithoutBatching(ctx context.Context, srcCS, sinkCS chunks.ChunkStore, walkAddrs WalkAddrs, hashes []hash.Hash, progressCh chan PullProgress) error {
// by increasing the batch size to MaxInt32 we effectively remove batching here.
return pull(ctx, srcCS, sinkCS, walkAddrs, hashes, progressCh, math.MaxInt32)
}
// concurrently pull all chunks from this batch that the sink is missing out of the source
func getChunks(ctx context.Context, srcCS chunks.ChunkStore, batch hash.HashSlice, sampleSize uint64, sampleCount uint64, updateProgress func(moreDone uint64, moreKnown uint64, moreApproxBytesWritten uint64)) (map[hash.Hash]*chunks.Chunk, error) {
mu := &sync.Mutex{}
neededChunks := map[hash.Hash]*chunks.Chunk{}
err := srcCS.GetMany(ctx, batch.HashSet(), func(ctx context.Context, c *chunks.Chunk) {
mu.Lock()
defer mu.Unlock()
neededChunks[c.Hash()] = c
// Randomly sample amount of data written
if rand.Float64() < bytesWrittenSampleRate {
sampleSize += uint64(len(snappy.Encode(nil, c.Data())))
sampleCount++
}
updateProgress(1, 0, sampleSize/uint64(math.Max(1, float64(sampleCount))))
})
if err != nil {
return nil, err
}
return neededChunks, nil
}
type WalkAddrs func(chunks.Chunk, func(hash.Hash, bool) error) error
// put the chunks that were downloaded into the sink IN ORDER and at the same time gather up an ordered, uniquified list
// of all the children of the chunks and add them to the list of the next level tree chunks.
func putChunks(ctx context.Context, wah WalkAddrs, sinkCS chunks.ChunkStore, hashes hash.HashSlice, neededChunks map[hash.Hash]*chunks.Chunk, nextLevel hash.HashSet, uniqueOrdered hash.HashSlice) (hash.HashSlice, error) {
for _, h := range hashes {
c := neededChunks[h]
err := sinkCS.Put(ctx, *c)
if err != nil {
return hash.HashSlice{}, err
}
err = wah(*c, func(h hash.Hash, _ bool) error {
if !nextLevel.Has(h) {
uniqueOrdered = append(uniqueOrdered, h)
nextLevel.Insert(h)
}
return nil
})
if err != nil {
return hash.HashSlice{}, err
}
}
return uniqueOrdered, nil
}
// ask sinkDB which of the next level's hashes it doesn't have, and add those chunks to the absent list which will need
// to be retrieved.
func nextLevelMissingChunks(ctx context.Context, sinkCS chunks.ChunkStore, nextLevel hash.HashSet, absent hash.HashSlice, uniqueOrdered hash.HashSlice) (hash.HashSlice, error) {
missingFromSink, err := sinkCS.HasMany(ctx, nextLevel)
if err != nil {
return hash.HashSlice{}, err
}
absent = absent[:0]
for _, h := range uniqueOrdered {
if missingFromSink.Has(h) {
absent = append(absent, h)
}
}
return absent, nil
}
-708
View File
@@ -1,708 +0,0 @@
// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// 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 pull
import (
"bytes"
"context"
"errors"
"io"
"os"
"reflect"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/dolthub/dolt/go/store/d"
"github.com/dolthub/dolt/go/store/datas"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/nbs"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/types"
)
const datasetID = "ds1"
func TestLocalToLocalPulls(t *testing.T) {
suite.Run(t, &LocalToLocalSuite{})
}
func TestRemoteToLocalPulls(t *testing.T) {
suite.Run(t, &RemoteToLocalSuite{})
}
func TestLocalToRemotePulls(t *testing.T) {
suite.Run(t, &LocalToRemoteSuite{})
}
func TestRemoteToRemotePulls(t *testing.T) {
suite.Run(t, &RemoteToRemoteSuite{})
}
func TestChunkJournalPulls(t *testing.T) {
suite.Run(t, &ChunkJournalSuite{})
}
type PullSuite struct {
suite.Suite
sinkCS chunks.ChunkStore
sourceCS chunks.ChunkStore
sinkVRW types.ValueReadWriter
sourceVRW types.ValueReadWriter
sinkDB datas.Database
sourceDB datas.Database
commitReads int // The number of reads triggered by commit differs across chunk store impls
}
type metricsChunkStore interface {
chunks.ChunkStore
Reads() int
Hases() int
Writes() int
}
func makeTestStoreViews() (ts1, ts2 *chunks.TestStoreView) {
st1, st2 := &chunks.TestStorage{}, &chunks.TestStorage{}
return st1.NewView(), st2.NewView()
}
type LocalToLocalSuite struct {
PullSuite
}
func (suite *LocalToLocalSuite) SetupTest() {
suite.sinkCS, suite.sourceCS = makeTestStoreViews()
sinkVRW, sourceVRW := types.NewValueStore(suite.sinkCS), types.NewValueStore(suite.sourceCS)
suite.sinkVRW, suite.sourceVRW = sinkVRW, sourceVRW
suite.sourceDB = datas.NewTypesDatabase(sourceVRW, tree.NewNodeStore(suite.sourceCS))
suite.sinkDB = datas.NewTypesDatabase(sinkVRW, tree.NewNodeStore(suite.sinkCS))
}
type RemoteToLocalSuite struct {
PullSuite
}
func (suite *RemoteToLocalSuite) SetupTest() {
suite.sinkCS, suite.sourceCS = makeTestStoreViews()
sinkVRW, sourceVRW := types.NewValueStore(suite.sinkCS), types.NewValueStore(suite.sourceCS)
suite.sinkVRW, suite.sourceVRW = sinkVRW, sourceVRW
suite.sourceDB = datas.NewTypesDatabase(sourceVRW, tree.NewNodeStore(suite.sourceCS))
suite.sinkDB = datas.NewTypesDatabase(sinkVRW, tree.NewNodeStore(suite.sinkCS))
}
type LocalToRemoteSuite struct {
PullSuite
}
func (suite *LocalToRemoteSuite) SetupTest() {
suite.sinkCS, suite.sourceCS = makeTestStoreViews()
sinkVRW, sourceVRW := types.NewValueStore(suite.sinkCS), types.NewValueStore(suite.sourceCS)
suite.sinkVRW, suite.sourceVRW = sinkVRW, sourceVRW
suite.sourceDB = datas.NewTypesDatabase(sourceVRW, tree.NewNodeStore(suite.sourceCS))
suite.sinkDB = datas.NewTypesDatabase(sinkVRW, tree.NewNodeStore(suite.sinkCS))
suite.commitReads = 1
}
type RemoteToRemoteSuite struct {
PullSuite
}
func (suite *RemoteToRemoteSuite) SetupTest() {
suite.sinkCS, suite.sourceCS = makeTestStoreViews()
sinkVRW, sourceVRW := types.NewValueStore(suite.sinkCS), types.NewValueStore(suite.sourceCS)
suite.sinkVRW, suite.sourceVRW = sinkVRW, sourceVRW
suite.sourceDB = datas.NewTypesDatabase(sourceVRW, tree.NewNodeStore(suite.sourceCS))
suite.sinkDB = datas.NewTypesDatabase(sinkVRW, tree.NewNodeStore(suite.sinkCS))
suite.commitReads = 1
}
type ChunkJournalSuite struct {
PullSuite
}
func (suite *ChunkJournalSuite) SetupTest() {
ctx := context.Background()
q := nbs.NewUnlimitedMemQuotaProvider()
nbf := types.Format_Default.VersionString()
path, err := os.MkdirTemp("", "remote")
suite.NoError(err)
sink, err := nbs.NewLocalJournalingStore(ctx, nbf, path, q)
suite.NoError(err)
path, err = os.MkdirTemp("", "local")
suite.NoError(err)
src, err := nbs.NewLocalJournalingStore(ctx, nbf, path, q)
suite.NoError(err)
suite.sinkCS, suite.sourceCS = sink, src
sinkVRW, sourceVRW := types.NewValueStore(suite.sinkCS), types.NewValueStore(suite.sourceCS)
suite.sinkVRW, suite.sourceVRW = sinkVRW, sourceVRW
suite.sourceDB = datas.NewTypesDatabase(sourceVRW, tree.NewNodeStore(suite.sourceCS))
suite.sinkDB = datas.NewTypesDatabase(sinkVRW, tree.NewNodeStore(suite.sinkCS))
suite.commitReads = 1
}
func (suite *PullSuite) TearDownTest() {
suite.sinkCS.Close()
suite.sourceCS.Close()
}
type progressTracker struct {
Ch chan PullProgress
doneCh chan []PullProgress
}
func startProgressTracker() *progressTracker {
pt := &progressTracker{make(chan PullProgress), make(chan []PullProgress)}
go func() {
progress := []PullProgress{}
for info := range pt.Ch {
progress = append(progress, info)
}
pt.doneCh <- progress
}()
return pt
}
func (pt *progressTracker) Validate(suite *PullSuite) {
close(pt.Ch)
progress := <-pt.doneCh
// Expecting exact progress would be unreliable and not necessary meaningful. Instead, just validate that it's useful and consistent.
suite.NotEmpty(progress)
first := progress[0]
suite.Zero(first.DoneCount)
suite.True(first.KnownCount > 0)
suite.Zero(first.ApproxWrittenBytes)
last := progress[len(progress)-1]
suite.True(last.DoneCount > 0)
suite.Equal(last.DoneCount, last.KnownCount)
for i, prog := range progress {
suite.True(prog.KnownCount >= prog.DoneCount)
if i > 0 {
prev := progress[i-1]
suite.True(prog.DoneCount >= prev.DoneCount)
suite.True(prog.ApproxWrittenBytes >= prev.ApproxWrittenBytes)
}
}
}
// Source:
//
// -3-> C(L2) -1-> N
// \ -2-> L1 -1-> N
// \ -1-> L0
//
// Sink: Nada
func (suite *PullSuite) TestPullEverything() {
var expectedReads int
mcs, metrics := suite.sinkCS.(metricsChunkStore)
if metrics {
expectedReads = mcs.Reads()
}
l := buildListOfHeight(2, suite.sourceVRW)
sourceAddr := suite.commitToSource(l, nil)
pt := startProgressTracker()
waf, err := types.WalkAddrsForChunkStore(suite.sourceCS)
suite.NoError(err)
err = Pull(context.Background(), suite.sourceCS, suite.sinkCS, waf, []hash.Hash{sourceAddr}, pt.Ch)
suite.NoError(err)
if metrics {
suite.True(expectedReads-suite.sinkCS.(metricsChunkStore).Reads() <= suite.commitReads)
}
pt.Validate(suite)
v := mustValue(suite.sinkVRW.ReadValue(context.Background(), sourceAddr))
suite.NotNil(v)
suite.True(l.Equals(mustGetCommittedValue(suite.sinkVRW, v)))
}
// Source:
//
// -6-> C3(L5) -1-> N
// . \ -5-> L4 -1-> N
// . \ -4-> L3 -1-> N
// . \ -3-> L2 -1-> N
// 5 \ -2-> L1 -1-> N
// . \ -1-> L0
// C2(L4) -1-> N
// . \ -4-> L3 -1-> N
// . \ -3-> L2 -1-> N
// . \ -2-> L1 -1-> N
// 3 \ -1-> L0
// .
// C1(L2) -1-> N
// \ -2-> L1 -1-> N
// \ -1-> L0
//
// Sink:
//
// -3-> C1(L2) -1-> N
// \ -2-> L1 -1-> N
// \ -1-> L0
func (suite *PullSuite) TestPullMultiGeneration() {
sinkL := buildListOfHeight(2, suite.sinkVRW)
suite.commitToSink(sinkL, nil)
var expectedReads int
mcs, metrics := suite.sinkCS.(metricsChunkStore)
if metrics {
expectedReads = mcs.Reads()
}
srcL := buildListOfHeight(2, suite.sourceVRW)
sourceAddr := suite.commitToSource(srcL, nil)
srcL = buildListOfHeight(4, suite.sourceVRW)
sourceAddr = suite.commitToSource(srcL, []hash.Hash{sourceAddr})
srcL = buildListOfHeight(5, suite.sourceVRW)
sourceAddr = suite.commitToSource(srcL, []hash.Hash{sourceAddr})
pt := startProgressTracker()
waf, err := types.WalkAddrsForChunkStore(suite.sourceCS)
suite.NoError(err)
err = Pull(context.Background(), suite.sourceCS, suite.sinkCS, waf, []hash.Hash{sourceAddr}, pt.Ch)
suite.NoError(err)
if metrics {
suite.True(expectedReads-suite.sinkCS.(metricsChunkStore).Reads() <= suite.commitReads)
}
pt.Validate(suite)
v, err := suite.sinkVRW.ReadValue(context.Background(), sourceAddr)
suite.NoError(err)
suite.NotNil(v)
suite.True(srcL.Equals(mustGetCommittedValue(suite.sinkVRW, v)))
}
// Source:
//
// -6-> C2(L5) -1-> N
// . \ -5-> L4 -1-> N
// . \ -4-> L3 -1-> N
// . \ -3-> L2 -1-> N
// 4 \ -2-> L1 -1-> N
// . \ -1-> L0
// C1(L3) -1-> N
// \ -3-> L2 -1-> N
// \ -2-> L1 -1-> N
// \ -1-> L0
//
// Sink:
//
// -5-> C3(L3') -1-> N
// . \ -3-> L2 -1-> N
// . \ \ -2-> L1 -1-> N
// . \ \ -1-> L0
// . \ - "oy!"
// 4
// .
// C1(L3) -1-> N
// \ -3-> L2 -1-> N
// \ -2-> L1 -1-> N
// \ -1-> L0
func (suite *PullSuite) TestPullDivergentHistory() {
sinkL := buildListOfHeight(3, suite.sinkVRW)
sinkAddr := suite.commitToSink(sinkL, nil)
srcL := buildListOfHeight(3, suite.sourceVRW)
sourceAddr := suite.commitToSource(srcL, nil)
var err error
sinkL, err = sinkL.Edit().Append(types.String("oy!")).List(context.Background())
suite.NoError(err)
sinkAddr = suite.commitToSink(sinkL, []hash.Hash{sinkAddr})
srcL, err = srcL.Edit().Set(1, buildListOfHeight(5, suite.sourceVRW)).List(context.Background())
suite.NoError(err)
sourceAddr = suite.commitToSource(srcL, []hash.Hash{sourceAddr})
var preReads int
mcs, metrics := suite.sinkCS.(metricsChunkStore)
if metrics {
preReads = mcs.Reads()
}
pt := startProgressTracker()
waf, err := types.WalkAddrsForChunkStore(suite.sourceCS)
suite.NoError(err)
err = Pull(context.Background(), suite.sourceCS, suite.sinkCS, waf, []hash.Hash{sourceAddr}, pt.Ch)
suite.NoError(err)
if metrics {
suite.True(preReads-suite.sinkCS.(metricsChunkStore).Reads() <= suite.commitReads)
}
pt.Validate(suite)
v, err := suite.sinkVRW.ReadValue(context.Background(), sourceAddr)
suite.NoError(err)
suite.NotNil(v)
suite.True(srcL.Equals(mustGetCommittedValue(suite.sinkVRW, v)))
}
// Source:
//
// -6-> C2(L4) -1-> N
// . \ -4-> L3 -1-> N
// . \ -3-> L2 -1-> N
// . \ - "oy!"
// 5 \ -2-> L1 -1-> N
// . \ -1-> L0
// C1(L4) -1-> N
// \ -4-> L3 -1-> N
// \ -3-> L2 -1-> N
// \ -2-> L1 -1-> N
// \ -1-> L0
//
// Sink:
//
// -5-> C1(L4) -1-> N
// \ -4-> L3 -1-> N
// \ -3-> L2 -1-> N
// \ -2-> L1 -1-> N
// \ -1-> L0
func (suite *PullSuite) TestPullUpdates() {
sinkL := buildListOfHeight(4, suite.sinkVRW)
suite.commitToSink(sinkL, nil)
var expectedReads int
mcs, metrics := suite.sinkCS.(metricsChunkStore)
if metrics {
expectedReads = mcs.Reads()
}
srcL := buildListOfHeight(4, suite.sourceVRW)
sourceAddr := suite.commitToSource(srcL, nil)
L3 := mustValue(mustValue(srcL.Get(context.Background(), 1)).(types.Ref).TargetValue(context.Background(), suite.sourceVRW)).(types.List)
L2 := mustValue(mustValue(L3.Get(context.Background(), 1)).(types.Ref).TargetValue(context.Background(), suite.sourceVRW)).(types.List)
L2Ed := L2.Edit().Append(mustRef(suite.sourceVRW.WriteValue(context.Background(), types.String("oy!"))))
L2, err := L2Ed.List(context.Background())
suite.NoError(err)
L3Ed := L3.Edit().Set(1, mustRef(suite.sourceVRW.WriteValue(context.Background(), L2)))
L3, err = L3Ed.List(context.Background())
suite.NoError(err)
srcLEd := srcL.Edit().Set(1, mustRef(suite.sourceVRW.WriteValue(context.Background(), L3)))
srcL, err = srcLEd.List(context.Background())
suite.NoError(err)
sourceAddr = suite.commitToSource(srcL, []hash.Hash{sourceAddr})
pt := startProgressTracker()
waf, err := types.WalkAddrsForChunkStore(suite.sourceCS)
suite.NoError(err)
err = Pull(context.Background(), suite.sourceCS, suite.sinkCS, waf, []hash.Hash{sourceAddr}, pt.Ch)
suite.NoError(err)
if metrics {
suite.True(expectedReads-suite.sinkCS.(metricsChunkStore).Reads() <= suite.commitReads)
}
pt.Validate(suite)
v, err := suite.sinkVRW.ReadValue(context.Background(), sourceAddr)
suite.NoError(err)
suite.NotNil(v)
suite.True(srcL.Equals(mustGetCommittedValue(suite.sinkVRW, v)))
}
func (suite *PullSuite) commitToSource(v types.Value, p []hash.Hash) hash.Hash {
db := suite.sourceDB
ds, err := db.GetDataset(context.Background(), datasetID)
suite.NoError(err)
ds, err = db.Commit(context.Background(), ds, v, datas.CommitOptions{Parents: p})
suite.NoError(err)
return mustHeadAddr(ds)
}
func (suite *PullSuite) commitToSink(v types.Value, p []hash.Hash) hash.Hash {
db := suite.sinkDB
ds, err := db.GetDataset(context.Background(), datasetID)
suite.NoError(err)
ds, err = db.Commit(context.Background(), ds, v, datas.CommitOptions{Parents: p})
suite.NoError(err)
return mustHeadAddr(ds)
}
func buildListOfHeight(height int, vrw types.ValueReadWriter) types.List {
unique := 0
l, err := types.NewList(context.Background(), vrw, types.Float(unique), types.Float(unique+1))
d.PanicIfError(err)
unique += 2
for i := 0; i < height; i++ {
r1, err := vrw.WriteValue(context.Background(), types.Float(unique))
d.PanicIfError(err)
r2, err := vrw.WriteValue(context.Background(), l)
d.PanicIfError(err)
unique++
l, err = types.NewList(context.Background(), vrw, r1, r2)
d.PanicIfError(err)
}
return l
}
type TestFailingTableFile struct {
fileID string
numChunks int
}
func (ttf *TestFailingTableFile) FileID() string {
return ttf.fileID
}
func (ttf *TestFailingTableFile) NumChunks() int {
return ttf.numChunks
}
func (ttf *TestFailingTableFile) Open(ctx context.Context) (io.ReadCloser, uint64, error) {
return io.NopCloser(bytes.NewReader([]byte{0x00})), 1, errors.New("this is a test error")
}
type TestTableFile struct {
fileID string
numChunks int
data []byte
}
func (ttf *TestTableFile) FileID() string {
return ttf.fileID
}
func (ttf *TestTableFile) NumChunks() int {
return ttf.numChunks
}
func (ttf *TestTableFile) Open(ctx context.Context) (io.ReadCloser, uint64, error) {
return io.NopCloser(bytes.NewReader(ttf.data)), uint64(len(ttf.data)), nil
}
type TestTableFileWriter struct {
fileID string
numChunks int
writer *bytes.Buffer
ttfs *TestTableFileStore
}
func (ttfWr *TestTableFileWriter) Write(data []byte) (int, error) {
return ttfWr.writer.Write(data)
}
func (ttfWr *TestTableFileWriter) Close(ctx context.Context) error {
data := ttfWr.writer.Bytes()
ttfWr.writer = nil
ttfWr.ttfs.mu.Lock()
defer ttfWr.ttfs.mu.Unlock()
ttfWr.ttfs.tableFiles[ttfWr.fileID] = &TestTableFile{ttfWr.fileID, ttfWr.numChunks, data}
return nil
}
type TestTableFileStore struct {
root hash.Hash
tableFiles map[string]*TestTableFile
mu sync.Mutex
}
var _ nbs.TableFileStore = &TestTableFileStore{}
func (ttfs *TestTableFileStore) Sources(ctx context.Context) (hash.Hash, []nbs.TableFile, []nbs.TableFile, error) {
ttfs.mu.Lock()
defer ttfs.mu.Unlock()
var tblFiles []nbs.TableFile
for _, tblFile := range ttfs.tableFiles {
tblFiles = append(tblFiles, tblFile)
}
return ttfs.root, tblFiles, []nbs.TableFile{}, nil
}
func (ttfs *TestTableFileStore) Size(ctx context.Context) (uint64, error) {
ttfs.mu.Lock()
defer ttfs.mu.Unlock()
sz := uint64(0)
for _, tblFile := range ttfs.tableFiles {
sz += uint64(len(tblFile.data))
}
return sz, nil
}
func (ttfs *TestTableFileStore) WriteTableFile(ctx context.Context, fileId string, numChunks int, contentHash []byte, getRd func() (io.ReadCloser, uint64, error)) error {
tblFile := &TestTableFileWriter{fileId, numChunks, bytes.NewBuffer(nil), ttfs}
rd, _, err := getRd()
if err != nil {
return err
}
defer rd.Close()
_, err = io.Copy(tblFile, rd)
if err != nil {
return err
}
return tblFile.Close(ctx)
}
// AddTableFilesToManifest adds table files to the manifest
func (ttfs *TestTableFileStore) AddTableFilesToManifest(ctx context.Context, fileIdToNumChunks map[string]int) error {
return nil
}
func (ttfs *TestTableFileStore) SetRootChunk(ctx context.Context, root, previous hash.Hash) error {
ttfs.root = root
return nil
}
type FlakeyTestTableFileStore struct {
*TestTableFileStore
GoodNow bool
}
func (f *FlakeyTestTableFileStore) Sources(ctx context.Context) (hash.Hash, []nbs.TableFile, []nbs.TableFile, error) {
if !f.GoodNow {
f.GoodNow = true
r, files, appendixFiles, _ := f.TestTableFileStore.Sources(ctx)
for i := range files {
files[i] = &TestFailingTableFile{files[i].FileID(), files[i].NumChunks()}
}
return r, files, appendixFiles, nil
}
return f.TestTableFileStore.Sources(ctx)
}
func (ttfs *TestTableFileStore) SupportedOperations() nbs.TableFileStoreOps {
return nbs.TableFileStoreOps{
CanRead: true,
CanWrite: true,
}
}
func (ttfs *TestTableFileStore) PruneTableFiles(ctx context.Context) error {
return chunks.ErrUnsupportedOperation
}
func TestClone(t *testing.T) {
hashBytes := [hash.ByteLen]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}
src := &TestTableFileStore{
root: hash.Of(hashBytes[:]),
tableFiles: map[string]*TestTableFile{
"file1": {
fileID: "file1",
numChunks: 1,
data: []byte("Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, "),
},
"file2": {
fileID: "file2",
numChunks: 2,
data: []byte("and nothing particular to interest me on shore, I thought I would sail about a little and see the watery "),
},
"file3": {
fileID: "file3",
numChunks: 3,
data: []byte("part of the world. It is a way I have of driving off the spleen and regulating the "),
},
"file4": {
fileID: "file4",
numChunks: 4,
data: []byte("circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly "),
},
"file5": {
fileID: "file5",
numChunks: 5,
data: []byte("November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing "),
},
},
}
dest := &TestTableFileStore{
root: hash.Hash{},
tableFiles: map[string]*TestTableFile{},
}
ctx := context.Background()
err := clone(ctx, src, dest, nil)
require.NoError(t, err)
err = dest.SetRootChunk(ctx, src.root, hash.Hash{})
require.NoError(t, err)
assert.True(t, reflect.DeepEqual(src, dest))
t.Run("WithFlakeyTableFileStore", func(t *testing.T) {
// After a Clone()'s TableFile.Open() or a Read from the TableFile
// fails, we retry with newly fetched Sources().
flakeySrc := &FlakeyTestTableFileStore{
TestTableFileStore: src,
}
dest = &TestTableFileStore{
root: hash.Hash{},
tableFiles: map[string]*TestTableFile{},
}
err := clone(ctx, flakeySrc, dest, nil)
require.NoError(t, err)
err = dest.SetRootChunk(ctx, flakeySrc.root, hash.Hash{})
require.NoError(t, err)
assert.True(t, reflect.DeepEqual(flakeySrc.TestTableFileStore, dest))
})
}
func mustList(l types.List, err error) types.List {
d.PanicIfError(err)
return l
}
func mustValue(val types.Value, err error) types.Value {
d.PanicIfError(err)
return val
}
func mustGetCommittedValue(vr types.ValueReader, c types.Value) types.Value {
v, err := datas.GetCommittedValue(context.Background(), vr, c)
d.PanicIfError(err)
d.PanicIfFalse(v != nil)
return v
}
func mustGetValue(v types.Value, found bool, err error) types.Value {
d.PanicIfError(err)
d.PanicIfFalse(found)
return v
}
func mustRef(ref types.Ref, err error) types.Ref {
d.PanicIfError(err)
return ref
}
func mustHeadAddr(ds datas.Dataset) hash.Hash {
addr, ok := ds.MaybeHeadAddr()
d.PanicIfFalse(ok)
return addr
}
+2
View File
@@ -60,6 +60,8 @@ type CmpChnkAndRefs struct {
refs map[hash.Hash]bool
}
type WalkAddrs func(chunks.Chunk, func(hash.Hash, bool) error) error
// Puller is used to sync data between to Databases
type Puller struct {
waf WalkAddrs
+12
View File
@@ -187,6 +187,18 @@ func (hs HashSet) InsertAll(other HashSet) {
}
}
func (hs HashSet) Equals(other HashSet) bool {
if hs.Size() != other.Size() {
return false
}
for h := range hs {
if !other.Has(h) {
return false
}
}
return true
}
func (hs HashSet) Empty() {
for h := range hs {
delete(hs, h)
+32
View File
@@ -64,6 +64,7 @@ type awsTablePersister struct {
}
var _ tablePersister = awsTablePersister{}
var _ tableFilePersister = awsTablePersister{}
type awsLimits struct {
partTarget, partMin, partMax uint64
@@ -108,6 +109,37 @@ func (s3p awsTablePersister) Exists(ctx context.Context, name addr, chunkCount u
)
}
func (s3p awsTablePersister) CopyTableFile(ctx context.Context, r io.ReadCloser, fileId string, chunkCount uint32) error {
var err error
defer func() {
cerr := r.Close()
if err == nil {
err = cerr
}
}()
data, err := io.ReadAll(r)
if err != nil {
return err
}
name, err := parseAddr(fileId)
if err != nil {
return err
}
if s3p.limits.tableFitsInDynamo(name, len(data), chunkCount) {
return s3p.ddb.Write(ctx, name, data)
}
return s3p.multipartUpload(ctx, data, fileId)
}
func (s3p awsTablePersister) Path() string {
return s3p.bucket
}
type s3UploadedPart struct {
idx int64
etag string
@@ -43,6 +43,10 @@ func benchmarkNovelWrite(refreshStore storeOpenFn, src *dataSource, t assert.Tes
return true
}
func getAddrsCb(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return nil, nil
}
func writeToEmptyStore(store chunks.ChunkStore, src *dataSource, t assert.TestingT) {
root, err := store.Root(context.Background())
assert.NoError(t, err)
@@ -50,11 +54,11 @@ func writeToEmptyStore(store chunks.ChunkStore, src *dataSource, t assert.Testin
chunx := goReadChunks(src)
for c := range chunx {
err := store.Put(context.Background(), *c)
err := store.Put(context.Background(), *c, getAddrsCb)
assert.NoError(t, err)
}
newRoot := chunks.NewChunk([]byte("root"))
err = store.Put(context.Background(), newRoot)
err = store.Put(context.Background(), newRoot, getAddrsCb)
assert.NoError(t, err)
success, err := store.Commit(context.Background(), newRoot.Hash(), root)
assert.NoError(t, err)
@@ -78,7 +82,7 @@ func benchmarkNoRefreshWrite(openStore storeOpenFn, src *dataSource, t assert.Te
assert.NoError(t, err)
chunx := goReadChunks(src)
for c := range chunx {
err := store.Put(context.Background(), *c)
err := store.Put(context.Background(), *c, getAddrsCb)
assert.NoError(t, err)
}
assert.NoError(t, store.Close())
+1 -1
View File
@@ -58,7 +58,7 @@ func (fb fileBlockStore) HasMany(ctx context.Context, hashes hash.HashSet) (pres
panic("not impl")
}
func (fb fileBlockStore) Put(ctx context.Context, c chunks.Chunk) error {
func (fb fileBlockStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCb) error {
_, err := io.Copy(fb.bw, bytes.NewReader(c.Data()))
return err
}
+1 -1
View File
@@ -51,7 +51,7 @@ func (nb nullBlockStore) HasMany(ctx context.Context, hashes hash.HashSet) (pres
panic("not impl")
}
func (nb nullBlockStore) Put(ctx context.Context, c chunks.Chunk) error {
func (nb nullBlockStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCb) error {
return nil
}
+30 -19
View File
@@ -115,10 +115,14 @@ func (suite *BlockStoreSuite) TestChunkStoreNotDir() {
suite.Error(err)
}
func getAddrsCb(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return nil, nil
}
func (suite *BlockStoreSuite) TestChunkStorePut() {
input := []byte("abc")
c := chunks.NewChunk(input)
err := suite.store.Put(context.Background(), c)
err := suite.store.Put(context.Background(), c, getAddrsCb)
suite.NoError(err)
h := c.Hash()
@@ -139,7 +143,7 @@ func (suite *BlockStoreSuite) TestChunkStorePut() {
// Re-writing the same data should cause a second put
c = chunks.NewChunk(input)
err = suite.store.Put(context.Background(), c)
err = suite.store.Put(context.Background(), c, getAddrsCb)
suite.NoError(err)
suite.Equal(h, c.Hash())
assertInputInStore(input, h, suite.store, suite.Assert())
@@ -151,14 +155,21 @@ func (suite *BlockStoreSuite) TestChunkStorePut() {
if suite.putCountFn != nil {
suite.Equal(2, suite.putCountFn())
}
// Put chunk with dangling ref should error
nc := chunks.NewChunk([]byte("bcd"))
err = suite.store.Put(context.Background(), nc, func(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return hash.NewHashSet(c.Hash()), nil
})
suite.Error(err)
}
func (suite *BlockStoreSuite) TestChunkStorePutMany() {
input1, input2 := []byte("abc"), []byte("def")
c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2)
err := suite.store.Put(context.Background(), c1)
err := suite.store.Put(context.Background(), c1, getAddrsCb)
suite.NoError(err)
err = suite.store.Put(context.Background(), c2)
err = suite.store.Put(context.Background(), c2, getAddrsCb)
suite.NoError(err)
rt, err := suite.store.Root(context.Background())
@@ -178,9 +189,9 @@ func (suite *BlockStoreSuite) TestChunkStorePutMany() {
func (suite *BlockStoreSuite) TestChunkStoreStatsSummary() {
input1, input2 := []byte("abc"), []byte("def")
c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2)
err := suite.store.Put(context.Background(), c1)
err := suite.store.Put(context.Background(), c1, getAddrsCb)
suite.NoError(err)
err = suite.store.Put(context.Background(), c2)
err = suite.store.Put(context.Background(), c2, getAddrsCb)
suite.NoError(err)
rt, err := suite.store.Root(context.Background())
@@ -201,9 +212,9 @@ func (suite *BlockStoreSuite) TestChunkStorePutMoreThanMemTable() {
_, err = rand.Read(input2)
suite.NoError(err)
c1, c2 := chunks.NewChunk(input1), chunks.NewChunk(input2)
err = suite.store.Put(context.Background(), c1)
err = suite.store.Put(context.Background(), c1, getAddrsCb)
suite.NoError(err)
err = suite.store.Put(context.Background(), c2)
err = suite.store.Put(context.Background(), c2, getAddrsCb)
suite.NoError(err)
rt, err := suite.store.Root(context.Background())
@@ -232,7 +243,7 @@ func (suite *BlockStoreSuite) TestChunkStoreGetMany() {
chnx := make([]chunks.Chunk, len(inputs))
for i, data := range inputs {
chnx[i] = chunks.NewChunk(data)
err = suite.store.Put(context.Background(), chnx[i])
err = suite.store.Put(context.Background(), chnx[i], getAddrsCb)
suite.NoError(err)
}
@@ -272,7 +283,7 @@ func (suite *BlockStoreSuite) TestChunkStoreHasMany() {
chunks.NewChunk([]byte("def")),
}
for _, c := range chnx {
err := suite.store.Put(context.Background(), c)
err := suite.store.Put(context.Background(), c, getAddrsCb)
suite.NoError(err)
}
@@ -305,7 +316,7 @@ func (suite *BlockStoreSuite) TestChunkStoreFlushOptimisticLockFail() {
interloper, err := suite.factory(context.Background(), suite.dir)
suite.NoError(err)
err = interloper.Put(context.Background(), c1)
err = interloper.Put(context.Background(), c1, getAddrsCb)
suite.NoError(err)
h, err := interloper.Root(context.Background())
suite.NoError(err)
@@ -313,7 +324,7 @@ func (suite *BlockStoreSuite) TestChunkStoreFlushOptimisticLockFail() {
suite.NoError(err)
suite.True(success)
err = suite.store.Put(context.Background(), c2)
err = suite.store.Put(context.Background(), c2, getAddrsCb)
suite.NoError(err)
h, err = suite.store.Root(context.Background())
suite.NoError(err)
@@ -354,7 +365,7 @@ func (suite *BlockStoreSuite) TestChunkStoreRebaseOnNoOpFlush() {
interloper, err := suite.factory(context.Background(), suite.dir)
suite.NoError(err)
err = interloper.Put(context.Background(), c1)
err = interloper.Put(context.Background(), c1, getAddrsCb)
suite.NoError(err)
root, err := interloper.Root(context.Background())
suite.NoError(err)
@@ -393,7 +404,7 @@ func (suite *BlockStoreSuite) TestChunkStorePutWithRebase() {
interloper, err := suite.factory(context.Background(), suite.dir)
suite.NoError(err)
err = interloper.Put(context.Background(), c1)
err = interloper.Put(context.Background(), c1, getAddrsCb)
suite.NoError(err)
h, err := interloper.Root(context.Background())
suite.NoError(err)
@@ -401,7 +412,7 @@ func (suite *BlockStoreSuite) TestChunkStorePutWithRebase() {
suite.NoError(err)
suite.True(success)
err = suite.store.Put(context.Background(), c2)
err = suite.store.Put(context.Background(), c2, getAddrsCb)
suite.NoError(err)
// Reading c2 via the API should work pre-rebase
@@ -456,7 +467,7 @@ func TestBlockStoreConjoinOnCommit(t *testing.T) {
t.Run("in memory blobstore persister", func(t *testing.T) {
testBlockStoreConjoinOnCommit(t, func(t *testing.T) tablePersister {
return &blobstorePersister{
bs: blobstore.NewInMemoryBlobstore(),
bs: blobstore.NewInMemoryBlobstore(""),
blockSize: 4096,
q: &UnlimitedQuotaProvider{},
}
@@ -500,7 +511,7 @@ func testBlockStoreConjoinOnCommit(t *testing.T, factory func(t *testing.T) tabl
root, err := smallTableStore.Root(context.Background())
require.NoError(t, err)
err = smallTableStore.Put(context.Background(), newChunk)
err = smallTableStore.Put(context.Background(), newChunk, getAddrsCb)
require.NoError(t, err)
success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root)
require.NoError(t, err)
@@ -532,7 +543,7 @@ func testBlockStoreConjoinOnCommit(t *testing.T, factory func(t *testing.T) tabl
root, err := smallTableStore.Root(context.Background())
require.NoError(t, err)
err = smallTableStore.Put(context.Background(), newChunk)
err = smallTableStore.Put(context.Background(), newChunk, getAddrsCb)
require.NoError(t, err)
success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root)
require.NoError(t, err)
@@ -569,7 +580,7 @@ func testBlockStoreConjoinOnCommit(t *testing.T, factory func(t *testing.T) tabl
root, err := smallTableStore.Root(context.Background())
require.NoError(t, err)
err = smallTableStore.Put(context.Background(), newChunk)
err = smallTableStore.Put(context.Background(), newChunk, getAddrsCb)
require.NoError(t, err)
success, err := smallTableStore.Commit(context.Background(), newChunk.Hash(), root)
require.NoError(t, err)
+38 -5
View File
@@ -17,8 +17,10 @@ package nbs
import (
"bytes"
"context"
"errors"
"github.com/dolthub/dolt/go/store/blobstore"
"github.com/dolthub/dolt/go/store/chunks"
)
const (
@@ -26,12 +28,11 @@ const (
)
type blobstoreManifest struct {
name string
bs blobstore.Blobstore
bs blobstore.Blobstore
}
func (bsm blobstoreManifest) Name() string {
return bsm.name
return bsm.bs.Path()
}
func manifestVersionAndContents(ctx context.Context, bs blobstore.Blobstore) (string, manifestContents, error) {
@@ -74,16 +75,48 @@ func (bsm blobstoreManifest) ParseIfExists(ctx context.Context, stats *Stats, re
// Update updates the contents of the manifest in the blobstore
func (bsm blobstoreManifest) Update(ctx context.Context, lastLock addr, newContents manifestContents, stats *Stats, writeHook func() error) (manifestContents, error) {
checker := func(upstream, contents manifestContents) error {
if contents.gcGen != upstream.gcGen {
return chunks.ErrGCGenerationExpired
}
return nil
}
return updateBSWithChecker(ctx, bsm.bs, checker, lastLock, newContents, writeHook)
}
func (bsm blobstoreManifest) UpdateGCGen(ctx context.Context, lastLock addr, newContents manifestContents, stats *Stats, writeHook func() error) (manifestContents, error) {
checker := func(upstream, contents manifestContents) error {
if contents.gcGen == upstream.gcGen {
return errors.New("UpdateGCGen() must update the garbage collection generation")
}
if contents.root != upstream.root {
return errors.New("UpdateGCGen() cannot update the root")
}
return nil
}
return updateBSWithChecker(ctx, bsm.bs, checker, lastLock, newContents, writeHook)
}
func updateBSWithChecker(ctx context.Context, bs blobstore.Blobstore, validate manifestChecker, lastLock addr, newContents manifestContents, writeHook func() error) (mc manifestContents, err error) {
if writeHook != nil {
panic("Write hooks not supported")
}
ver, contents, err := manifestVersionAndContents(ctx, bsm.bs)
ver, contents, err := manifestVersionAndContents(ctx, bs)
if err != nil && !blobstore.IsNotFoundError(err) {
return manifestContents{}, err
}
// this is where we assert that gcGen is correct
err = validate(contents, newContents)
if err != nil {
return manifestContents{}, err
}
if contents.lock == lastLock {
buffer := bytes.NewBuffer(make([]byte, 64*1024)[:0])
err := writeManifest(buffer, newContents)
@@ -92,7 +125,7 @@ func (bsm blobstoreManifest) Update(ctx context.Context, lastLock addr, newConte
return manifestContents{}, err
}
_, err = bsm.bs.CheckAndPut(ctx, ver, manifestFile, buffer)
_, err = bs.CheckAndPut(ctx, ver, manifestFile, buffer)
if err != nil {
if !blobstore.IsCheckAndPutError(err) {
+130 -38
View File
@@ -15,14 +15,18 @@
package nbs
import (
"bytes"
"context"
"errors"
"io"
"time"
"github.com/google/uuid"
"github.com/dolthub/dolt/go/store/blobstore"
"github.com/dolthub/dolt/go/store/chunks"
)
const (
tableRecordsExt = ".records"
tableTailExt = ".tail"
)
type blobstorePersister struct {
@@ -32,54 +36,115 @@ type blobstorePersister struct {
}
var _ tablePersister = &blobstorePersister{}
var _ tableFilePersister = &blobstorePersister{}
// Persist makes the contents of mt durable. Chunks already present in
// |haver| may be dropped in the process.
func (bsp *blobstorePersister) Persist(ctx context.Context, mt *memTable, haver chunkReader, stats *Stats) (chunkSource, error) {
name, data, chunkCount, err := mt.write(haver, stats)
address, data, chunkCount, err := mt.write(haver, stats)
if err != nil {
return emptyChunkSource{}, err
} else if chunkCount == 0 {
return emptyChunkSource{}, nil
}
name := address.String()
if chunkCount == 0 {
return emptyChunkSource{}, nil
// persist this table in two parts to facilitate later conjoins
records, tail := splitTableParts(data, chunkCount)
// first write table records and tail (index+footer) as separate blobs
if _, err = bsp.bs.Put(ctx, name+tableRecordsExt, bytes.NewBuffer(records)); err != nil {
return emptyChunkSource{}, err
}
_, err = blobstore.PutBytes(ctx, bsp.bs, name.String(), data)
if err != nil {
if _, err = bsp.bs.Put(ctx, name+tableTailExt, bytes.NewBuffer(tail)); err != nil {
return emptyChunkSource{}, err
}
// then concatenate into a final blob
if _, err = bsp.bs.Concatenate(ctx, name, []string{name + tableRecordsExt, name + tableTailExt}); err != nil {
return emptyChunkSource{}, err
}
bsTRA := &bsTableReaderAt{name.String(), bsp.bs}
return newReaderFromIndexData(ctx, bsp.q, data, name, bsTRA, bsp.blockSize)
rdr := &bsTableReaderAt{name, bsp.bs}
return newReaderFromIndexData(ctx, bsp.q, data, address, rdr, bsp.blockSize)
}
// ConjoinAll (Not currently implemented) conjoins all chunks in |sources| into a single,
// new chunkSource.
// ConjoinAll implements tablePersister.
func (bsp *blobstorePersister) ConjoinAll(ctx context.Context, sources chunkSources, stats *Stats) (chunkSource, error) {
plan, err := planConcatenateConjoin(sources, stats)
var sized []sourceWithSize
for _, src := range sources {
sized = append(sized, sourceWithSize{src, src.currentSize()})
}
plan, err := planConjoin(sized, stats)
if err != nil {
return nil, err
}
address := nameFromSuffixes(plan.suffixes())
name := address.String()
// conjoin must contiguously append the chunk records of |sources|, but the raw content
// of each source contains a chunk index in the tail. Blobstore does not expose a range
// copy (GCP Storage limitation), so we must create sub-objects from each source that
// contain only chunk records. We make an effort to store these sub-objects on Persist(),
// but we will create them in getRecordsSubObjects if necessary.
conjoinees := make([]string, 0, len(sources)+1)
for _, src := range sources {
conjoinees = append(conjoinees, src.hash().String())
for _, src := range plan.sources.sws {
sub, err := bsp.getRecordsSubObject(ctx, src.source)
if err != nil {
return nil, err
}
conjoinees = append(conjoinees, sub)
}
idxKey := uuid.New().String()
if _, err = blobstore.PutBytes(ctx, bsp.bs, idxKey, plan.mergedIndex); err != nil {
// first concatenate all the sub-objects to create a composite sub-object
if _, err = bsp.bs.Concatenate(ctx, name+tableRecordsExt, conjoinees); err != nil {
return nil, err
}
conjoinees = append(conjoinees, idxKey) // mergedIndex goes last
if _, err = blobstore.PutBytes(ctx, bsp.bs, name+tableTailExt, plan.mergedIndex); err != nil {
return nil, err
}
// then concatenate into a final blob
if _, err = bsp.bs.Concatenate(ctx, name, []string{name + tableRecordsExt, name + tableTailExt}); err != nil {
return emptyChunkSource{}, err
}
name := nameFromSuffixes(plan.suffixes())
if _, err = bsp.bs.Concatenate(ctx, name.String(), conjoinees); err != nil {
return nil, err
return newBSChunkSource(ctx, bsp.bs, address, plan.chunkCount, bsp.q, stats)
}
func (bsp *blobstorePersister) getRecordsSubObject(ctx context.Context, cs chunkSource) (name string, err error) {
name = cs.hash().String() + tableRecordsExt
// first check if we created this sub-object on Persist()
ok, err := bsp.bs.Exists(ctx, name)
if err != nil {
return "", err
} else if ok {
return name, nil
}
return newBSChunkSource(ctx, bsp.bs, name, plan.chunkCount, bsp.q, stats)
// otherwise create the sub-object from |table|
// (requires a round-trip for remote blobstores)
cnt, err := cs.count()
if err != nil {
return "", err
}
off := tableTailOffset(cs.currentSize(), cnt)
rng := blobstore.NewBlobRange(0, int64(off))
rdr, _, err := bsp.bs.Get(ctx, cs.hash().String(), rng)
if err != nil {
return "", err
}
defer func() {
if cerr := rdr.Close(); cerr != nil {
err = cerr
}
}()
if _, err = bsp.bs.Put(ctx, name, rdr); err != nil {
return "", err
}
return name, nil
}
// Open a table named |name|, containing |chunkCount| chunks.
@@ -92,13 +157,35 @@ func (bsp *blobstorePersister) Exists(ctx context.Context, name addr, chunkCount
}
func (bsp *blobstorePersister) PruneTableFiles(ctx context.Context, contents manifestContents, t time.Time) error {
return chunks.ErrUnsupportedOperation
return nil
}
func (bsp *blobstorePersister) Close() error {
return nil
}
func (bsp *blobstorePersister) Path() string {
return ""
}
func (bsp *blobstorePersister) CopyTableFile(ctx context.Context, r io.ReadCloser, fileId string, chunkCount uint32) error {
var err error
defer func() {
cerr := r.Close()
if err == nil {
err = cerr
}
}()
_, err = bsp.bs.Put(ctx, fileId, r)
if err != nil {
return err
}
return err
}
type bsTableReaderAt struct {
key string
bs blobstore.Blobstore
@@ -151,6 +238,10 @@ func newBSChunkSource(ctx context.Context, bs blobstore.Blobstore, name addr, ch
return nil, err
}
if chunkCount != index.chunkCount() {
return nil, errors.New("unexpected chunk count")
}
tr, err := newTableReader(index, &bsTableReaderAt{name.String(), bs}, s3BlockSize)
if err != nil {
_ = index.Close()
@@ -159,16 +250,17 @@ func newBSChunkSource(ctx context.Context, bs blobstore.Blobstore, name addr, ch
return &chunkSourceAdapter{tr, name}, nil
}
// planConcatenateConjoin computes a conjoin plan for tablePersisters that conjoin
// by concatenating existing chunk sources (leaving behind old chunk indexes, footers).
func planConcatenateConjoin(sources chunkSources, stats *Stats) (compactionPlan, error) {
var sized []sourceWithSize
for _, src := range sources {
index, err := src.index()
if err != nil {
return compactionPlan{}, err
}
sized = append(sized, sourceWithSize{src, index.tableFileSize()})
}
return planConjoin(sized, stats)
// splitTableParts separates a table into chunk records and meta data.
//
// +----------------------+-------+--------+
// table format: | Chunk Record 0 ... N | Index | Footer |
// +----------------------+-------+--------+
func splitTableParts(data []byte, count uint32) (records, tail []byte) {
o := tableTailOffset(uint64(len(data)), count)
records, tail = data[:o], data[o:]
return
}
func tableTailOffset(size uint64, count uint32) uint64 {
return size - (indexSize(count) + footerSize)
}
+7 -3
View File
@@ -91,7 +91,7 @@ func TestConjoin(t *testing.T) {
t.Run("in-memory blobstore persister", func(t *testing.T) {
testConjoin(t, func(*testing.T) tablePersister {
return &blobstorePersister{
bs: blobstore.NewInMemoryBlobstore(),
bs: blobstore.NewInMemoryBlobstore(""),
blockSize: 4096,
q: &UnlimitedQuotaProvider{},
}
@@ -148,11 +148,15 @@ func testConjoin(t *testing.T, factory func(t *testing.T) tablePersister) {
for _, src := range expectSrcs {
err := extractAllChunks(ctx, src, func(rec extractRecord) {
var ok bool
for _, src := range actualSrcs {
for _, act := range actualSrcs {
var err error
ok, err = src.has(rec.a)
ok, err = act.has(rec.a)
require.NoError(t, err)
var buf []byte
if ok {
buf, err = act.get(ctx, rec.a, stats)
require.NoError(t, err)
assert.Equal(t, rec.data, buf)
break
}
}
+37
View File
@@ -78,6 +78,43 @@ func (ftp *fsTablePersister) Path() string {
return ftp.dir
}
func (ftp *fsTablePersister) CopyTableFile(ctx context.Context, r io.ReadCloser, fileId string, chunkCount uint32) error {
tn, err := func() (n string, err error) {
defer func() {
cerr := r.Close()
if err == nil {
err = cerr
}
}()
var temp *os.File
temp, err = tempfiles.MovableTempFileProvider.NewFile(ftp.dir, tempTablePrefix)
if err != nil {
return "", err
}
defer func() {
cerr := temp.Close()
if err == nil {
err = cerr
}
}()
_, err = io.Copy(temp, r)
if err != nil {
return "", err
}
return temp.Name(), nil
}()
if err != nil {
return err
}
path := filepath.Join(ftp.dir, fileId)
return file.Rename(tn, path)
}
func (ftp *fsTablePersister) persistTable(ctx context.Context, name addr, data []byte, chunkCount uint32, stats *Stats) (cs chunkSource, err error) {
if chunkCount == 0 {
return emptyChunkSource{}, nil
+10 -48
View File
@@ -17,13 +17,7 @@ package nbs
import (
"context"
"fmt"
"io"
"os"
"path"
"strings"
"github.com/dolthub/dolt/go/libraries/utils/file"
"github.com/dolthub/dolt/go/store/util/tempfiles"
)
type gcErrAccum map[string]error
@@ -63,7 +57,7 @@ func (gcc *gcCopier) addChunk(ctx context.Context, c CompressedChunk) error {
return gcc.writer.AddCmpChunk(c)
}
func (gcc *gcCopier) copyTablesToDir(ctx context.Context, destDir string) (ts []tableSpec, err error) {
func (gcc *gcCopier) copyTablesToDir(ctx context.Context, tfp tableFilePersister) (ts []tableSpec, err error) {
var filename string
filename, err = gcc.writer.Finish()
if err != nil {
@@ -78,19 +72,18 @@ func (gcc *gcCopier) copyTablesToDir(ctx context.Context, destDir string) (ts []
_ = gcc.writer.Remove()
}()
filepath := path.Join(destDir, filename)
var addr addr
addr, err = parseAddr(filename)
if err != nil {
return nil, err
}
if info, err := os.Stat(filepath); err == nil {
// file already exists
if gcc.writer.ContentLength() != uint64(info.Size()) {
return nil, fmt.Errorf("'%s' already exists with different contents.", filepath)
}
exists, err := tfp.Exists(ctx, addr, uint32(gcc.writer.ChunkCount()), nil)
if err != nil {
return nil, err
}
if exists {
return []tableSpec{
{
name: addr,
@@ -99,44 +92,13 @@ func (gcc *gcCopier) copyTablesToDir(ctx context.Context, destDir string) (ts []
}, nil
}
// Otherwise, write the file.
var tf string
tf, err = func() (tf string, err error) {
var temp *os.File
temp, err = tempfiles.MovableTempFileProvider.NewFile(destDir, tempTablePrefix)
if err != nil {
return "", err
}
defer func() {
cerr := temp.Close()
if err == nil {
err = cerr
}
}()
r, err := gcc.writer.Reader()
if err != nil {
return "", err
}
defer func() {
cerr := r.Close()
if err == nil {
err = cerr
}
}()
_, err = io.Copy(temp, r)
if err != nil {
return "", err
}
return temp.Name(), nil
}()
r, err := gcc.writer.Reader()
if err != nil {
return nil, err
}
err = file.Rename(tf, filepath)
// Otherwise, write the file.
err = tfp.CopyTableFile(ctx, r, filename, uint32(gcc.writer.ChunkCount()))
if err != nil {
return nil, err
}
+30 -3
View File
@@ -16,6 +16,7 @@ package nbs
import (
"context"
"fmt"
"io"
"path/filepath"
"strings"
@@ -150,12 +151,36 @@ func (gcs *GenerationalNBS) HasMany(ctx context.Context, hashes hash.HashSet) (a
return gcs.newGen.HasMany(ctx, notInOldGen)
}
func (gcs *GenerationalNBS) errorIfDangling(ctx context.Context, addrs hash.HashSet) error {
absent, err := gcs.HasMany(ctx, addrs)
if err != nil {
return err
}
if len(absent) != 0 {
s := absent.String()
return fmt.Errorf("Found dangling references to %s", s)
}
return nil
}
// Put caches c in the ChunkSource. Upon return, c must be visible to
// subsequent Get and Has calls, but must not be persistent until a call
// to Flush(). Put may be called concurrently with other calls to Put(),
// Get(), GetMany(), Has() and HasMany().
func (gcs *GenerationalNBS) Put(ctx context.Context, c chunks.Chunk) error {
return gcs.newGen.Put(ctx, c)
func (gcs *GenerationalNBS) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCb) error {
addrs, err := getAddrs(ctx, c)
if err != nil {
return err
}
err = gcs.errorIfDangling(ctx, addrs)
if err != nil {
return err
}
return gcs.newGen.Put(ctx, c, func(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return nil, nil
})
}
// Returns the NomsVersion with which this ChunkSource is compatible.
@@ -232,7 +257,9 @@ func (gcs *GenerationalNBS) copyToOldGen(ctx context.Context, hashes hash.HashSe
var putErr error
err = gcs.newGen.GetMany(ctx, notInOldGen, func(ctx context.Context, chunk *chunks.Chunk) {
if putErr == nil {
putErr = gcs.oldGen.Put(ctx, *chunk)
putErr = gcs.oldGen.Put(ctx, *chunk, func(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return nil, nil
})
}
})
@@ -131,7 +131,7 @@ func requireChunks(t *testing.T, ctx context.Context, chunks []chunks.Chunk, gen
func putChunks(t *testing.T, ctx context.Context, chunks []chunks.Chunk, cs chunks.ChunkStore, indexesIn map[int]bool, chunkIndexes ...int) {
for _, idx := range chunkIndexes {
err := cs.Put(ctx, chunks[idx])
err := cs.Put(ctx, chunks[idx], getAddrsCb)
require.NoError(t, err)
indexesIn[idx] = true
}
+4
View File
@@ -222,6 +222,10 @@ func (j *chunkJournal) Path() string {
return filepath.Dir(j.path)
}
func (j *chunkJournal) CopyTableFile(ctx context.Context, r io.ReadCloser, fileId string, chunkCount uint32) error {
return j.persister.CopyTableFile(ctx, r, fileId, chunkCount)
}
// Name implements manifest.
func (j *chunkJournal) Name() string {
return j.path
+7 -7
View File
@@ -113,7 +113,7 @@ func TestChunkStoreCommit(t *testing.T) {
newRootChunk := chunks.NewChunk([]byte("new root"))
newRoot := newRootChunk.Hash()
err = store.Put(context.Background(), newRootChunk)
err = store.Put(context.Background(), newRootChunk, getAddrsCb)
require.NoError(t, err)
success, err := store.Commit(context.Background(), newRoot, hash.Hash{})
require.NoError(t, err)
@@ -128,7 +128,7 @@ func TestChunkStoreCommit(t *testing.T) {
secondRootChunk := chunks.NewChunk([]byte("newer root"))
secondRoot := secondRootChunk.Hash()
err = store.Put(context.Background(), secondRootChunk)
err = store.Put(context.Background(), secondRootChunk, getAddrsCb)
require.NoError(t, err)
success, err = store.Commit(context.Background(), secondRoot, newRoot)
require.NoError(t, err)
@@ -241,13 +241,13 @@ func TestChunkStoreManifestPreemptiveOptimisticLockFail(t *testing.T) {
}()
chunk := chunks.NewChunk([]byte("hello"))
err = interloper.Put(context.Background(), chunk)
err = interloper.Put(context.Background(), chunk, getAddrsCb)
require.NoError(t, err)
assert.True(interloper.Commit(context.Background(), chunk.Hash(), hash.Hash{}))
// Try to land a new chunk in store, which should fail AND not persist the contents of store.mt
chunk = chunks.NewChunk([]byte("goodbye"))
err = store.Put(context.Background(), chunk)
err = store.Put(context.Background(), chunk, getAddrsCb)
require.NoError(t, err)
assert.NotNil(store.mt)
assert.False(store.Commit(context.Background(), chunk.Hash(), hash.Hash{}))
@@ -296,7 +296,7 @@ func TestChunkStoreCommitLocksOutFetch(t *testing.T) {
}
rootChunk := chunks.NewChunk([]byte("new root"))
err = store.Put(context.Background(), rootChunk)
err = store.Put(context.Background(), rootChunk, getAddrsCb)
require.NoError(t, err)
h, err := store.Root(context.Background())
require.NoError(t, err)
@@ -352,7 +352,7 @@ func TestChunkStoreSerializeCommits(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
err := interloper.Put(context.Background(), interloperChunk)
err := interloper.Put(context.Background(), interloperChunk, getAddrsCb)
require.NoError(t, err)
h, err := interloper.Root(context.Background())
require.NoError(t, err)
@@ -364,7 +364,7 @@ func TestChunkStoreSerializeCommits(t *testing.T) {
updateCount++
}
err = store.Put(context.Background(), storeChunk)
err = store.Put(context.Background(), storeChunk, getAddrsCb)
require.NoError(t, err)
h, err := store.Root(context.Background())
require.NoError(t, err)
+5 -5
View File
@@ -57,11 +57,11 @@ func TestStats(t *testing.T) {
c1, c2, c3, c4, c5 := chunks.NewChunk(i1), chunks.NewChunk(i2), chunks.NewChunk(i3), chunks.NewChunk(i4), chunks.NewChunk(i5)
// These just go to mem table, only operation stats
err = store.Put(context.Background(), c1)
err = store.Put(context.Background(), c1, getAddrsCb)
require.NoError(t, err)
err = store.Put(context.Background(), c2)
err = store.Put(context.Background(), c2, getAddrsCb)
require.NoError(t, err)
err = store.Put(context.Background(), c3)
err = store.Put(context.Background(), c3, getAddrsCb)
require.NoError(t, err)
assert.Equal(uint64(3), stats(store).PutLatency.Samples())
assert.Equal(uint64(0), stats(store).PersistLatency.Samples())
@@ -131,14 +131,14 @@ func TestStats(t *testing.T) {
// Force a conjoin
store.c = inlineConjoiner{2}
err = store.Put(context.Background(), c4)
err = store.Put(context.Background(), c4, getAddrsCb)
require.NoError(t, err)
h, err = store.Root(context.Background())
require.NoError(t, err)
_, err = store.Commit(context.Background(), h, h)
require.NoError(t, err)
err = store.Put(context.Background(), c5)
err = store.Put(context.Background(), c5, getAddrsCb)
require.NoError(t, err)
h, err = store.Root(context.Background())
require.NoError(t, err)
+38 -54
View File
@@ -26,7 +26,6 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"sync"
"sync/atomic"
@@ -40,11 +39,9 @@ import (
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"github.com/dolthub/dolt/go/libraries/utils/file"
"github.com/dolthub/dolt/go/store/blobstore"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/util/tempfiles"
)
var (
@@ -455,7 +452,7 @@ func NewGCSStore(ctx context.Context, nbfVerStr string, bucketName, path string,
func NewBSStore(ctx context.Context, nbfVerStr string, bs blobstore.Blobstore, memTableSize uint64, q MemoryQuotaProvider) (*NomsBlockStore, error) {
cacheOnce.Do(makeGlobalCaches)
mm := makeManifestManager(blobstoreManifest{"manifest", bs})
mm := makeManifestManager(blobstoreManifest{bs})
p := &blobstorePersister{bs, s3BlockSize, q}
return newNomsBlockStore(ctx, nbfVerStr, mm, p, q, inlineConjoiner{defaultMaxTables}, memTableSize)
@@ -578,9 +575,32 @@ func (nbs *NomsBlockStore) WithoutConjoiner() *NomsBlockStore {
}
}
func (nbs *NomsBlockStore) Put(ctx context.Context, c chunks.Chunk) error {
func (nbs *NomsBlockStore) errorIfDangling(ctx context.Context, addrs hash.HashSet) error {
absent, err := nbs.HasMany(ctx, addrs)
if err != nil {
return err
}
if len(absent) != 0 {
s := absent.String()
return fmt.Errorf("Found dangling references to %s", s)
}
return nil
}
func (nbs *NomsBlockStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCb) error {
t1 := time.Now()
a := addr(c.Hash())
addrs, err := getAddrs(ctx, c)
if err != nil {
return err
}
err = nbs.errorIfDangling(ctx, addrs)
if err != nil {
return err
}
success, err := nbs.addChunk(ctx, a, c.Data())
if err != nil {
return err
@@ -1276,10 +1296,8 @@ func (nbs *NomsBlockStore) chunkSourcesByAddr() (map[addr]chunkSource, error) {
func (nbs *NomsBlockStore) SupportedOperations() TableFileStoreOps {
var ok bool
switch nbs.p.(type) {
case *fsTablePersister, *chunkJournal:
ok = true
}
_, ok = nbs.p.(tableFilePersister)
return TableFileStoreOps{
CanRead: true,
CanWrite: ok,
@@ -1290,62 +1308,29 @@ func (nbs *NomsBlockStore) SupportedOperations() TableFileStoreOps {
func (nbs *NomsBlockStore) Path() (string, bool) {
if tfp, ok := nbs.p.(tableFilePersister); ok {
return tfp.Path(), true
switch p := tfp.(type) {
case *fsTablePersister, *chunkJournal:
return p.Path(), true
default:
return "", false
}
}
return "", false
}
// WriteTableFile will read a table file from the provided reader and write it to the TableFileStore
func (nbs *NomsBlockStore) WriteTableFile(ctx context.Context, fileId string, numChunks int, contentHash []byte, getRd func() (io.ReadCloser, uint64, error)) error {
var fsPersister *fsTablePersister
switch t := nbs.p.(type) {
case *fsTablePersister:
fsPersister = t
case *chunkJournal:
fsPersister = t.persister
default:
tfp, ok := nbs.p.(tableFilePersister)
if !ok {
return errors.New("Not implemented")
}
tn, err := func() (n string, err error) {
var r io.ReadCloser
r, _, err = getRd()
if err != nil {
return "", err
}
defer func() {
cerr := r.Close()
if err == nil {
err = cerr
}
}()
var temp *os.File
temp, err = tempfiles.MovableTempFileProvider.NewFile(fsPersister.dir, tempTablePrefix)
if err != nil {
return "", err
}
defer func() {
cerr := temp.Close()
if err == nil {
err = cerr
}
}()
_, err = io.Copy(temp, r)
if err != nil {
return "", err
}
return temp.Name(), nil
}()
r, _, err := getRd()
if err != nil {
return err
}
path := filepath.Join(fsPersister.dir, fileId)
return file.Rename(tn, path)
return tfp.CopyTableFile(ctx, r, fileId, uint32(numChunks))
}
// AddTableFilesToManifest adds table files to the manifest
@@ -1496,7 +1481,6 @@ func (nbs *NomsBlockStore) copyMarkedChunks(ctx context.Context, keepChunks <-ch
if !ok {
return nil, fmt.Errorf("NBS does not support copying garbage collection")
}
path := tfp.Path()
LOOP:
for {
@@ -1526,7 +1510,7 @@ LOOP:
return nil, ctx.Err()
}
}
return gcc.copyTablesToDir(ctx, path)
return gcc.copyTablesToDir(ctx, tfp)
}
// todo: what's the optimal table size to copy to?
+5 -5
View File
@@ -133,7 +133,7 @@ func TestConcurrentPuts(t *testing.T) {
c := makeChunk(uint32(i))
hashes[i] = c.Hash()
errgrp.Go(func() error {
err := st.Put(ctx, c)
err := st.Put(ctx, c, getAddrsCb)
require.NoError(t, err)
return nil
})
@@ -277,7 +277,7 @@ func TestNBSCopyGC(t *testing.T) {
tossers := makeChunkSet(64, 64)
for _, c := range keepers {
err := st.Put(ctx, c)
err := st.Put(ctx, c, getAddrsCb)
require.NoError(t, err)
}
for h, c := range keepers {
@@ -293,7 +293,7 @@ func TestNBSCopyGC(t *testing.T) {
assert.Equal(t, chunks.Chunk{}, c)
}
for _, c := range tossers {
err := st.Put(ctx, c)
err := st.Put(ctx, c, getAddrsCb)
require.NoError(t, err)
}
for h, c := range tossers {
@@ -363,7 +363,7 @@ func prepStore(ctx context.Context, t *testing.T, assert *assert.Assertions) (*f
rootChunk := chunks.NewChunk([]byte("root"))
rootHash := rootChunk.Hash()
err = store.Put(ctx, rootChunk)
err = store.Put(ctx, rootChunk, getAddrsCb)
require.NoError(t, err)
success, err := store.Commit(ctx, rootHash, hash.Hash{})
require.NoError(t, err)
@@ -562,7 +562,7 @@ func TestNBSCommitRetainsAppendix(t *testing.T) {
// Make second Commit
secondRootChunk := chunks.NewChunk([]byte("newer root"))
secondRoot := secondRootChunk.Hash()
err = store.Put(ctx, secondRootChunk)
err = store.Put(ctx, secondRootChunk, getAddrsCb)
require.NoError(t, err)
success, err := store.Commit(ctx, secondRoot, rootChunk.Hash())
require.NoError(t, err)
+6 -1
View File
@@ -63,7 +63,12 @@ type tablePersister interface {
type tableFilePersister interface {
tablePersister
// Path returns the file system path.
// CopyTableFile copies the table file with the given fileId from the reader to the TableFileStore.
CopyTableFile(ctx context.Context, r io.ReadCloser, fileId string, chunkCount uint32) error
// Path returns the file system path. Use CopyTableFile instead of Path to
// copy a file to the TableFileStore. Path cannot be removed because it's used
// in remotesrv.
Path() string
}
+12 -7
View File
@@ -30,7 +30,9 @@ func TestAddressMap(t *testing.T) {
t.Run("smoke test address map", func(t *testing.T) {
ctx := context.Background()
ns := tree.NewTestNodeStore()
pairs := randomAddressPairs(10_000)
addr, err := ns.Write(ctx, tree.NewEmptyTestNode())
require.NoError(t, err)
pairs := randomAddressPairs(10_000, addr)
empty, err := NewEmptyAddressMap(ns)
require.NoError(t, err)
@@ -56,25 +58,28 @@ func TestAddressMap(t *testing.T) {
})
}
type addrPair [2][]byte
type addrPair struct {
n []byte
h hash.Hash
}
func (a addrPair) name() string {
return string(a[0])
return string(a.n)
}
func (a addrPair) addr() hash.Hash {
return hash.New(a[1])
return a.h
}
func randomAddressPairs(cnt int) (ap []addrPair) {
func randomAddressPairs(cnt int, addr hash.Hash) (ap []addrPair) {
buf := make([]byte, cnt*20*2)
testRand.Read(buf)
ap = make([]addrPair, cnt)
for i := range ap {
o := i * 40
ap[i][0] = buf[o : o+20]
ap[i][1] = buf[o+20 : o+40]
ap[i].n = buf[o : o+20]
ap[i].h = addr
}
return
}
+11 -5
View File
@@ -37,13 +37,16 @@ func TestArtifactMapEditing(t *testing.T) {
am, err := NewArtifactMapFromTuples(ctx, ns, srcKd)
require.NoError(t, err)
addr, err := ns.Write(ctx, tree.NewEmptyTestNode())
require.NoError(t, err)
for _, n := range []int{10, 100, 1000} {
t.Run(fmt.Sprintf("%d inserts", n), func(t *testing.T) {
edt := am.Editor()
for i := 0; i < n; i++ {
srcKb.PutInt16(0, int16(i))
key1 := srcKb.Build(sharedPool)
err = edt.Add(ctx, key1, hash.Of([]byte("left")), ArtifactTypeConflict, []byte("{}"))
err = edt.Add(ctx, key1, addr, ArtifactTypeConflict, []byte("{}"))
require.NoError(t, err)
}
nm, err := edt.Flush(ctx)
@@ -89,26 +92,29 @@ func TestMergeArtifactMaps(t *testing.T) {
expected, err := NewArtifactMapFromTuples(ctx, ns, srcKd)
require.NoError(t, err)
addr, err := ns.Write(ctx, tree.NewEmptyTestNode())
require.NoError(t, err)
leftEdt := left.Editor()
rightEdt := right.Editor()
srcKb.PutInt16(0, 1)
key1 := srcKb.Build(sharedPool)
err = leftEdt.Add(ctx, key1, hash.Of([]byte("left")), ArtifactTypeConflict, []byte("{}"))
err = leftEdt.Add(ctx, key1, addr, ArtifactTypeConflict, []byte("{}"))
require.NoError(t, err)
left, err = leftEdt.Flush(ctx)
require.NoError(t, err)
srcKb.PutInt16(0, 2)
key2 := srcKb.Build(sharedPool)
err = rightEdt.Add(ctx, key2, hash.Of([]byte("right")), ArtifactTypeConflict, []byte("{}"))
err = rightEdt.Add(ctx, key2, addr, ArtifactTypeConflict, []byte("{}"))
require.NoError(t, err)
right, err = rightEdt.Flush(ctx)
expectedEdt := expected.Editor()
err = expectedEdt.Add(ctx, key1, hash.Of([]byte("left")), ArtifactTypeConflict, []byte("{}"))
err = expectedEdt.Add(ctx, key1, addr, ArtifactTypeConflict, []byte("{}"))
require.NoError(t, err)
err = expectedEdt.Add(ctx, key2, hash.Of([]byte("right")), ArtifactTypeConflict, []byte("{}"))
err = expectedEdt.Add(ctx, key2, addr, ArtifactTypeConflict, []byte("{}"))
require.NoError(t, err)
expected, err = expectedEdt.Flush(ctx)
+22 -15
View File
@@ -17,7 +17,6 @@ package prolly
import (
"context"
"errors"
"fmt"
"io"
"testing"
@@ -70,10 +69,12 @@ func TestCommitClosure(t *testing.T) {
t.Run("Insert", func(t *testing.T) {
cc, err := NewEmptyCommitClosure(ns)
require.NoError(t, err)
addr, err := ns.Write(ctx, tree.NewEmptyTestNode())
require.NoError(t, err)
e := cc.Editor()
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, addr))
assert.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, addr))
assert.NoError(t, err)
cc, err = e.Flush(ctx)
assert.NoError(t, err)
@@ -94,9 +95,9 @@ func TestCommitClosure(t *testing.T) {
assert.True(t, errors.Is(err, io.EOF))
e = cc.Editor()
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, addr))
assert.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, addr))
assert.NoError(t, err)
cc, err = e.Flush(ctx)
assert.NoError(t, err)
@@ -108,10 +109,12 @@ func TestCommitClosure(t *testing.T) {
t.Run("Diff", func(t *testing.T) {
ccl, err := NewEmptyCommitClosure(ns)
require.NoError(t, err)
addr, err := ns.Write(ctx, tree.NewEmptyTestNode())
require.NoError(t, err)
e := ccl.Editor()
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, addr))
assert.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, addr))
assert.NoError(t, err)
ccl, err = e.Flush(ctx)
assert.NoError(t, err)
@@ -122,19 +125,19 @@ func TestCommitClosure(t *testing.T) {
ccr, err := NewEmptyCommitClosure(ns)
require.NoError(t, err)
e = ccr.Editor()
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 0, addr))
assert.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, addr))
assert.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, hash.Parse("00000000000000000000000000000001")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 1, addr))
assert.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 2, hash.Parse("00000000000000000000000000000000")))
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), 2, addr))
assert.NoError(t, err)
ccr, err = e.Flush(ctx)
assert.NoError(t, err)
ccrc, err := ccr.Count()
require.NoError(t, err)
assert.Equal(t, 4, ccrc)
assert.Equal(t, 3, ccrc)
var numadds, numdels int
err = DiffCommitClosures(ctx, ccl, ccr, func(ctx context.Context, d tree.Diff) error {
@@ -147,7 +150,7 @@ func TestCommitClosure(t *testing.T) {
})
assert.Error(t, err)
assert.True(t, errors.Is(err, io.EOF))
assert.Equal(t, 2, numadds)
assert.Equal(t, 1, numadds)
assert.Equal(t, 0, numdels)
})
@@ -156,7 +159,9 @@ func TestCommitClosure(t *testing.T) {
require.NoError(t, err)
e := cc.Editor()
for i := 0; i < 4096; i++ {
err := e.Add(ctx, NewCommitClosureKey(ns.Pool(), uint64(i), hash.Parse(fmt.Sprintf("%0.32d", i))))
addr, err := ns.Write(ctx, tree.NewEmptyTestNode())
require.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), uint64(i), addr))
require.NoError(t, err)
}
cc, err = e.Flush(ctx)
@@ -190,7 +195,9 @@ func TestCommitClosure(t *testing.T) {
require.NoError(t, err)
e := cc.Editor()
for i := 0; i < 4096; i++ {
err := e.Add(ctx, NewCommitClosureKey(ns.Pool(), uint64(i), hash.Parse(fmt.Sprintf("%0.32d", i))))
addr, err := ns.Write(ctx, tree.NewEmptyTestNode())
require.NoError(t, err)
err = e.Add(ctx, NewCommitClosureKey(ns.Pool(), uint64(i), addr))
require.NoError(t, err)
}
cc, err = e.Flush(ctx)
+12 -1
View File
@@ -18,6 +18,8 @@ import (
"context"
"sync"
"github.com/dolthub/dolt/go/store/prolly/message"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/pool"
@@ -147,7 +149,16 @@ func (ns nodeStore) Write(ctx context.Context, nd Node) (hash.Hash, error) {
c := chunks.NewChunk(nd.bytes())
assertTrue(c.Size() > 0, "cannot write empty chunk to ChunkStore")
if err := ns.store.Put(ctx, c); err != nil {
getAddrs := func(ctx context.Context, ch chunks.Chunk) (addrs hash.HashSet, err error) {
addrs = hash.NewHashSet()
err = message.WalkAddresses(ctx, ch.Data(), func(ctx context.Context, a hash.Hash) error {
addrs.Insert(a)
return nil
})
return
}
if err := ns.store.Put(ctx, c, getAddrs); err != nil {
return hash.Hash{}, err
}
ns.cache.insert(c.Hash(), nd)
+4
View File
@@ -154,6 +154,10 @@ func ShuffleTuplePairs(items [][2]val.Tuple) {
})
}
func NewEmptyTestNode() Node {
return newLeafNode(nil, nil)
}
func newLeafNode(keys, values []Item) Node {
kk := make([][]byte, len(keys))
for i := range keys {
+5 -1
View File
@@ -468,6 +468,10 @@ func (t *testProtocol) NewDatabase(sp Spec) (datas.Database, error) {
return datas.NewDatabase(cs), nil
}
func getAddrsCb(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return nil, nil
}
func TestExternalProtocol(t *testing.T) {
assert := assert.New(t)
tp := testProtocol{}
@@ -481,7 +485,7 @@ func TestExternalProtocol(t *testing.T) {
cs := sp.NewChunkStore(context.Background())
assert.Equal("foo", tp.name)
c := chunks.NewChunk([]byte("hi!"))
err = cs.Put(context.Background(), c)
err = cs.Put(context.Background(), c, getAddrsCb)
assert.NoError(err)
ok, err := cs.Has(context.Background(), c.Hash())
assert.NoError(err)
+2 -1
View File
@@ -1881,6 +1881,7 @@ func TestMapTypeAfterMutations(t *testing.T) {
}
func TestCompoundMapWithValuesOfEveryType(t *testing.T) {
t.Skip("NewSet fails with dangling ref error TODO(taylor)")
assert := assert.New(t)
vrw := newTestValueStore()
@@ -1913,7 +1914,7 @@ func TestCompoundMapWithValuesOfEveryType(t *testing.T) {
k := Float(i)
kvs = append(kvs, k, v)
m, err = m.Edit().Set(k, v).Map(context.Background())
require.NoError(t, err)
require.NoError(t, err) // danging ref error
}
assert.Equal(len(kvs)/2, int(m.Len()))
+34 -98
View File
@@ -175,6 +175,15 @@ func (sm SerialMessage) Less(nbf *NomsBinFormat, other LesserValuable) (bool, er
const SerialMessageRefHeight = 1024
func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
return sm.walkAddrs(nbf, func(addr hash.Hash) error {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
return cb(r)
})
}
func (sm SerialMessage) walkAddrs(nbf *NomsBinFormat, cb func(addr hash.Hash) error) error {
switch serial.GetFileID(sm) {
case serial.StoreRootFileID:
var msg serial.StoreRoot
@@ -184,7 +193,7 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
}
if msg.AddressMapLength() > 0 {
mapbytes := msg.AddressMapBytes()
return SerialMessage(mapbytes).walkRefs(nbf, cb)
return SerialMessage(mapbytes).walkAddrs(nbf, cb)
}
case serial.TagFileID:
var msg serial.Tag
@@ -192,53 +201,27 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
if err != nil {
return err
}
addr := hash.New(msg.CommitAddrBytes())
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
return cb(r)
return cb(hash.New(msg.CommitAddrBytes()))
case serial.WorkingSetFileID:
var msg serial.WorkingSet
err := serial.InitWorkingSetRoot(&msg, []byte(sm), serial.MessagePrefixSz)
if err != nil {
return err
}
addr := hash.New(msg.WorkingRootAddrBytes())
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(hash.New(msg.WorkingRootAddrBytes())); err != nil {
return err
}
if msg.StagedRootAddrLength() != 0 {
addr = hash.New(msg.StagedRootAddrBytes())
r, err = constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(hash.New(msg.StagedRootAddrBytes())); err != nil {
return err
}
}
mergeState := msg.MergeState(nil)
if mergeState != nil {
addr = hash.New(mergeState.PreWorkingRootAddrBytes())
r, err = constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
if err = cb(hash.New(mergeState.PreWorkingRootAddrBytes())); err != nil {
return err
}
if err = cb(r); err != nil {
return err
}
addr = hash.New(mergeState.FromCommitAddrBytes())
r, err = constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(hash.New(mergeState.FromCommitAddrBytes())); err != nil {
return err
}
}
@@ -248,17 +231,13 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
if err != nil {
return err
}
err = SerialMessage(msg.TablesBytes()).walkRefs(nbf, cb)
err = SerialMessage(msg.TablesBytes()).walkAddrs(nbf, cb)
if err != nil {
return err
}
addr := hash.New(msg.ForeignKeyAddrBytes())
if !addr.IsEmpty() {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
@@ -268,84 +247,55 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
if err != nil {
return err
}
addr := hash.New(msg.SchemaBytes())
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
err = cb(r)
err = cb(hash.New(msg.SchemaBytes()))
if err != nil {
return err
}
confs := msg.Conflicts(nil)
addr = hash.New(confs.DataBytes())
addr := hash.New(confs.DataBytes())
if !addr.IsEmpty() {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
addr = hash.New(confs.OurSchemaBytes())
if !addr.IsEmpty() {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
addr = hash.New(confs.TheirSchemaBytes())
if !addr.IsEmpty() {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
addr = hash.New(confs.AncestorSchemaBytes())
if !addr.IsEmpty() {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
addr = hash.New(msg.ViolationsBytes())
if !addr.IsEmpty() {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
addr = hash.New(msg.ArtifactsBytes())
if !addr.IsEmpty() {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
err = SerialMessage(msg.SecondaryIndexesBytes()).walkRefs(nbf, cb)
err = SerialMessage(msg.SecondaryIndexesBytes()).walkAddrs(nbf, cb)
if err != nil {
return err
}
@@ -358,9 +308,11 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
if err != nil {
return err
}
return v.walkRefs(nbf, cb)
return v.walkRefs(nbf, func(ref Ref) error {
return cb(ref.TargetHash())
})
} else {
return SerialMessage(mapbytes).walkRefs(nbf, cb)
return SerialMessage(mapbytes).walkAddrs(nbf, cb)
}
case serial.CommitFileID:
parents, err := SerialCommitParentAddrs(nbf, sm)
@@ -368,11 +320,7 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
return err
}
for _, addr := range parents {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
@@ -382,21 +330,13 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
return err
}
addr := hash.New(msg.RootBytes())
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
addr = hash.New(msg.ParentClosureBytes())
if !addr.IsEmpty() {
r, err = constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
if err = cb(r); err != nil {
if err = cb(addr); err != nil {
return err
}
}
@@ -414,11 +354,7 @@ func (sm SerialMessage) walkRefs(nbf *NomsBinFormat, cb RefCallback) error {
fallthrough
case serial.CommitClosureFileID:
return message.WalkAddresses(context.TODO(), serial.Message(sm), func(ctx context.Context, addr hash.Hash) error {
r, err := constructRef(nbf, addr, PrimitiveTypeMap[ValueKind], SerialMessageRefHeight)
if err != nil {
return err
}
return cb(r)
return cb(addr)
})
default:
return fmt.Errorf("unsupported SerialMessage message with FileID: %s", serial.GetFileID(sm))
+2 -1
View File
@@ -1197,6 +1197,7 @@ func TestSetTypeAfterMutations(t *testing.T) {
}
func TestChunkedSetWithValuesOfEveryType(t *testing.T) {
t.Skip("NewSet fails with dangling ref error TODO(taylor)")
assert := assert.New(t)
vs := newTestValueStore()
@@ -1225,7 +1226,7 @@ func TestChunkedSetWithValuesOfEveryType(t *testing.T) {
}
s, err := NewSet(context.Background(), vs, vals...)
require.NoError(t, err)
require.NoError(t, err) // dangling ref error
for i := 1; s.asSequence().isLeaf(); i++ {
v := Float(i)
vals = append(vals, v)
+38 -16
View File
@@ -88,16 +88,39 @@ type ValueStore struct {
versOnce sync.Once
}
func PanicIfDangling(ctx context.Context, unresolved hash.HashSet, cs chunks.ChunkStore) {
func ErrorIfDangling(ctx context.Context, unresolved hash.HashSet, cs chunks.ChunkStore) error {
absent, err := cs.HasMany(ctx, unresolved)
// TODO: fix panics
d.PanicIfError(err)
if err != nil {
return err
}
if len(absent) != 0 {
s := absent.String()
d.Panic("Found dangling references to %s", s)
return fmt.Errorf("Found dangling references to %s", s)
}
return nil
}
func AddrsFromNomsValue(ctx context.Context, c chunks.Chunk, nbf *NomsBinFormat) (addrs hash.HashSet, err error) {
addrs = hash.NewHashSet()
if NomsKind(c.Data()[0]) == SerialMessageKind {
err = SerialMessage(c.Data()).walkAddrs(nbf, func(a hash.Hash) error {
addrs.Insert(a)
return nil
})
return
}
err = walkRefs(c.Data(), nbf, func(r Ref) error {
addrs.Insert(r.TargetHash())
return nil
})
return
}
func (lvs *ValueStore) getAddrs(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return AddrsFromNomsValue(ctx, c, lvs.nbf)
}
const (
@@ -396,15 +419,13 @@ func (lvs *ValueStore) bufferChunk(ctx context.Context, v Value, c chunks.Chunk,
// cheap enough that it would be possible to get back
// cache-locality in our flushes without ref heights.
if lvs.enforceCompleteness {
err := v.walkRefs(lvs.nbf, func(r Ref) error {
lvs.unresolvedRefs.Insert(r.TargetHash())
return nil
})
addrs, err := lvs.getAddrs(ctx, c)
if err != nil {
return err
}
lvs.unresolvedRefs.InsertAll(addrs)
}
return lvs.cs.Put(ctx, c)
return lvs.cs.Put(ctx, c, lvs.getAddrs)
}
d.PanicIfTrue(height == 0)
@@ -415,7 +436,7 @@ func (lvs *ValueStore) bufferChunk(ctx context.Context, v Value, c chunks.Chunk,
}
put := func(h hash.Hash, c chunks.Chunk) error {
err := lvs.cs.Put(ctx, c)
err := lvs.cs.Put(ctx, c, lvs.getAddrs)
if err != nil {
return err
@@ -535,8 +556,7 @@ func (lvs *ValueStore) Flush(ctx context.Context) error {
func (lvs *ValueStore) flush(ctx context.Context, current hash.Hash) error {
put := func(h hash.Hash, chunk chunks.Chunk) error {
err := lvs.cs.Put(ctx, chunk)
err := lvs.cs.Put(ctx, chunk, lvs.getAddrs)
if err != nil {
return err
}
@@ -570,8 +590,7 @@ func (lvs *ValueStore) flush(ctx context.Context, current hash.Hash) error {
}
for _, c := range lvs.bufferedChunks {
// Can't use put() because it's wrong to delete from a lvs.bufferedChunks while iterating it.
err := lvs.cs.Put(ctx, c)
err := lvs.cs.Put(ctx, c, lvs.getAddrs)
if err != nil {
return err
}
@@ -599,7 +618,10 @@ func (lvs *ValueStore) flush(ctx context.Context, current hash.Hash) error {
}
}
PanicIfDangling(ctx, lvs.unresolvedRefs, lvs.cs)
err = ErrorIfDangling(ctx, lvs.unresolvedRefs, lvs.cs)
if err != nil {
return err
}
}
return nil
+5 -24
View File
@@ -157,12 +157,12 @@ func (cbs *checkingChunkStore) expect(rs ...Ref) {
}
}
func (cbs *checkingChunkStore) Put(ctx context.Context, c chunks.Chunk) error {
func (cbs *checkingChunkStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCb) error {
if cbs.a.NotZero(len(cbs.expectedOrder), "Unexpected Put of %s", c.Hash()) {
cbs.a.Equal(cbs.expectedOrder[0], c.Hash())
cbs.expectedOrder = cbs.expectedOrder[1:]
}
return cbs.ChunkStore.Put(context.Background(), c)
return cbs.ChunkStore.Put(context.Background(), c, getAddrs)
}
func (cbs *checkingChunkStore) Flush() {
@@ -318,8 +318,8 @@ func TestPanicOnBadVersion(t *testing.T) {
})
}
func TestPanicIfDangling(t *testing.T) {
assert := assert.New(t)
func TestErrorIfDangling(t *testing.T) {
t.Skip("WriteValue errors with dangling ref error")
vs := newTestValueStore()
r, err := NewRef(Bool(true), vs.Format())
@@ -329,29 +329,10 @@ func TestPanicIfDangling(t *testing.T) {
_, err = vs.WriteValue(context.Background(), l)
require.NoError(t, err)
assert.Panics(func() {
rt, err := vs.Root(context.Background())
require.NoError(t, err)
_, err = vs.Commit(context.Background(), rt, rt)
require.NoError(t, err)
})
}
func TestSkipEnforceCompleteness(t *testing.T) {
vs := newTestValueStore()
vs.SetEnforceCompleteness(false)
r, err := NewRef(Bool(true), vs.Format())
require.NoError(t, err)
l, err := NewList(context.Background(), vs, r)
require.NoError(t, err)
_, err = vs.WriteValue(context.Background(), l)
require.NoError(t, err)
rt, err := vs.Root(context.Background())
require.NoError(t, err)
_, err = vs.Commit(context.Background(), rt, rt)
require.NoError(t, err)
require.Error(t, err)
}
func TestGC(t *testing.T) {
+28 -3
View File
@@ -16,6 +16,7 @@ package valuefile
import (
"context"
"fmt"
"sort"
"sync"
@@ -101,7 +102,9 @@ func (f *FileValueStore) WriteValue(ctx context.Context, v types.Value) (types.R
return types.Ref{}, err
}
err = f.Put(ctx, c)
err = f.Put(ctx, c, func(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return types.AddrsFromNomsValue(ctx, c, f.nbf)
})
if err != nil {
return types.Ref{}, err
@@ -168,8 +171,30 @@ func (f *FileValueStore) HasMany(ctx context.Context, hashes hash.HashSet) (abse
return absent, nil
}
// Put puts a chunk inton the store
func (f *FileValueStore) Put(ctx context.Context, c chunks.Chunk) error {
func (f *FileValueStore) errorIfDangling(ctx context.Context, addrs hash.HashSet) error {
absent, err := f.HasMany(ctx, addrs)
if err != nil {
return err
}
if len(absent) != 0 {
s := absent.String()
return fmt.Errorf("Found dangling references to %s", s)
}
return nil
}
// Put puts a chunk into the store
func (f *FileValueStore) Put(ctx context.Context, c chunks.Chunk, getAddrs chunks.GetAddrsCb) error {
addrs, err := getAddrs(ctx, c)
if err != nil {
return err
}
err = f.errorIfDangling(ctx, addrs)
if err != nil {
return err
}
f.chunkLock.Lock()
defer f.chunkLock.Unlock()
+5 -3
View File
@@ -218,7 +218,7 @@ func read(ctx context.Context, rd io.Reader) (hash.Hash, *FileValueStore, error)
if err != nil {
if err == io.EOF {
err = fmt.Errorf("EOF read while tring to get nbf format len - %w", ErrCorruptNVF)
err = fmt.Errorf("EOF read while trying to get nbf format len - %w", ErrCorruptNVF)
}
return hash.Hash{}, nil, err
@@ -228,7 +228,7 @@ func read(ctx context.Context, rd io.Reader) (hash.Hash, *FileValueStore, error)
if err != nil {
if err == io.EOF {
err = fmt.Errorf("EOF read while tring to get nbf format string - %w", ErrCorruptNVF)
err = fmt.Errorf("EOF read while trying to get nbf format string - %w", ErrCorruptNVF)
}
return hash.Hash{}, nil, err
@@ -307,7 +307,9 @@ func read(ctx context.Context, rd io.Reader) (hash.Hash, *FileValueStore, error)
return hash.Hash{}, nil, errors.New("data corrupted")
}
err = store.Put(ctx, ch)
err = store.Put(ctx, ch, func(ctx context.Context, c chunks.Chunk) (hash.HashSet, error) {
return nil, nil
})
if err != nil {
return hash.Hash{}, nil, err
+1 -5
View File
@@ -113,8 +113,6 @@ var CopiedNomsFiles []CopiedNomsFile = []CopiedNomsFile{
{Path: "store/cmd/noms/noms_show.go", NomsPath: "cmd/noms/noms_show.go", HadCopyrightNotice: true},
{Path: "store/cmd/noms/noms_show_test.go", NomsPath: "cmd/noms/noms_show_test.go", HadCopyrightNotice: true},
{Path: "store/cmd/noms/noms_stats.go", NomsPath: "cmd/noms/noms_stats.go", HadCopyrightNotice: true},
{Path: "store/cmd/noms/noms_sync.go", NomsPath: "cmd/noms/noms_sync.go", HadCopyrightNotice: true},
{Path: "store/cmd/noms/noms_sync_test.go", NomsPath: "cmd/noms/noms_sync_test.go", HadCopyrightNotice: true},
{Path: "store/cmd/noms/noms_version.go", NomsPath: "cmd/noms/noms_version.go", HadCopyrightNotice: true},
{Path: "store/cmd/noms/noms_version_test.go", NomsPath: "cmd/noms/noms_version_test.go", HadCopyrightNotice: true},
{Path: "store/config/config.go", NomsPath: "go/config/config.go", HadCopyrightNotice: true},
@@ -132,8 +130,6 @@ var CopiedNomsFiles []CopiedNomsFile = []CopiedNomsFile{
{Path: "store/datas/database_test.go", NomsPath: "go/datas/database_test.go", HadCopyrightNotice: true},
{Path: "store/datas/dataset.go", NomsPath: "go/datas/dataset.go", HadCopyrightNotice: true},
{Path: "store/datas/dataset_test.go", NomsPath: "go/datas/dataset_test.go", HadCopyrightNotice: true},
{Path: "store/datas/pull/pull.go", NomsPath: "go/datas/pull.go", HadCopyrightNotice: true},
{Path: "store/datas/pull/pull_test.go", NomsPath: "go/datas/pull_test.go", HadCopyrightNotice: true},
{Path: "store/diff/apply_patch.go", NomsPath: "go/diff/apply_patch.go", HadCopyrightNotice: true},
{Path: "store/diff/apply_patch_test.go", NomsPath: "go/diff/apply_patch_test.go", HadCopyrightNotice: true},
{Path: "store/diff/diff.go", NomsPath: "go/diff/diff.go", HadCopyrightNotice: true},
@@ -385,7 +381,7 @@ func CheckGo() bool {
}
return nil
})
for path, _ := range nomsLookup {
for path := range nomsLookup {
fmt.Printf("ERROR: Missing noms file from CopiedNomsFiles: %v\n", path)
fmt.Printf(" Please update with new location or remove the reference in ./utils/copyrightshdrs/")
failed = true
@@ -106,6 +106,7 @@ make_it() {
}
@test "deleted-branches: calling DOLT_CHECKOUT on SQL connection with existing branch revision specifier when dolt_default_branch is invalid does not panic" {
skip "Will fix in a future PR"
make_it
start_sql_server "dolt_repo_$$"
@@ -122,6 +123,7 @@ make_it() {
}
@test "deleted-branches: calling DOLT_CHECKOUT on SQL connection with existing branch revision specifier set to existing branch when default branch is deleted does not panic" {
skip "Will fix in a future PR"
make_it
dolt branch -c to_keep to_checkout
+48 -3
View File
@@ -35,7 +35,11 @@ teardown() {
run grep CREATE doltdump.sql
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 3 ]
[ "${#lines[@]}" -eq 4 ]
run grep "DATABASE IF NOT EXISTS" doltdump.sql
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 1 ]
run grep FOREIGN_KEY_CHECKS=0 doltdump.sql
[ "$status" -eq 0 ]
@@ -62,6 +66,47 @@ teardown() {
[[ "$output" =~ "Rows inserted: 6 Rows updated: 0 Rows deleted: 0" ]] || false
}
@test "dump: SQL type - no-create-db flag" {
dolt sql -q "CREATE TABLE new_table(pk int primary key);"
dolt sql -q "INSERT INTO new_table VALUES (1);"
dolt sql -q "CREATE TABLE warehouse(warehouse_id int primary key, warehouse_name longtext);"
dolt sql -q "INSERT into warehouse VALUES (1, 'UPS'), (2, 'TV'), (3, 'Table');"
dolt sql -q "create table enums (a varchar(10) primary key, b enum('one','two','three'))"
dolt sql -q "insert into enums values ('abc', 'one'), ('def', 'two')"
run dolt dump --no-create-db
[ "$status" -eq 0 ]
[[ "$output" =~ "Successfully exported data." ]] || false
[ -f doltdump.sql ]
run grep "CREATE DATABASE" doltdump.sql
[ "$status" -eq 1 ]
}
@test "dump: SQL type - database name is reserved word/keyword" {
dolt sql -q "CREATE DATABASE \`interval\`;"
cd interval
dolt sql -q "CREATE TABLE new_table(pk int primary key);"
dolt sql -q "INSERT INTO new_table VALUES (1);"
dolt sql -q "CREATE TABLE warehouse(warehouse_id int primary key, warehouse_name longtext);"
dolt sql -q "INSERT into warehouse VALUES (1, 'UPS'), (2, 'TV'), (3, 'Table');"
dolt sql -q "create table enums (a varchar(10) primary key, b enum('one','two','three'))"
dolt sql -q "insert into enums values ('abc', 'one'), ('def', 'two')"
run dolt dump
[ "$status" -eq 0 ]
[[ "$output" =~ "Successfully exported data." ]] || false
[ -f doltdump.sql ]
run grep "CREATE DATABASE IF NOT EXISTS \`interval\`" doltdump.sql
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 1 ]
run dolt sql -b < doltdump.sql
[ "$status" -eq 0 ]
[[ "$output" =~ "Rows inserted: 6 Rows updated: 0 Rows deleted: 0" ]] || false
}
@test "dump: SQL type - compare tables in database with tables imported file " {
dolt branch new_branch
dolt sql -q "CREATE TABLE new_table(pk int primary key);"
@@ -314,7 +359,7 @@ teardown() {
run grep CREATE doltdump.sql
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 2 ]
[ "${#lines[@]}" -eq 3 ]
run grep INSERT doltdump.sql
[ "$status" -eq 1 ]
@@ -340,7 +385,7 @@ teardown() {
run grep CREATE dumpfile.sql
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 3 ]
[ "${#lines[@]}" -eq 4 ]
}
@test "dump: SQL type - with directory name given" {
@@ -383,6 +383,85 @@ teardown() {
[ "$output" = "" ]
}
@test "remotes-sql-server: connect to remote branch that does not exist locally" {
skiponwindows "Missing dependencies"
cd repo1
dolt checkout -b feature
dolt commit -am "first commit"
dolt push remote1 feature
dolt checkout main
dolt push remote1 main
cd ../repo2
dolt fetch
run dolt branch
[[ ! "$output" =~ "feature" ]] || false
start_sql_server repo2
# No data on main
run dolt sql-client --use-db repo2 -P $PORT -u dolt -q "show tables"
[ $status -eq 0 ]
[ "$output" = "" ]
run dolt sql-client --use-db repo2/feature -P $PORT -u dolt -q "select active_branch()"
[ $status -eq 0 ]
[[ "$output" =~ "feature" ]] || false
[[ ! "$output" =~ "main" ]] || false
# connecting to remote branch that does not exist creates new local branch and sets upstream
run dolt sql-client --use-db repo2/feature -P $PORT -u dolt -q "call dolt_commit('--allow-empty', '-m', 'empty'); call dolt_push()"
[ $status -eq 0 ]
[[ ! "$output" =~ "the current branch has no upstream branch" ]] || false
run dolt sql-client --use-db repo2/feature -P $PORT -u dolt -q "show tables"
[ $status -eq 0 ]
[[ "$output" =~ "Tables_in_repo2/feature" ]] || false
[[ "$output" =~ "test" ]] || false
run dolt branch
[[ "$output" =~ "feature" ]] || false
cd ../repo1
dolt checkout feature
dolt pull remote1 feature
run dolt log -n 1 --oneline
[[ "$output" =~ "empty" ]] || false
}
@test "remotes-sql-server: connect to remote tracking branch fails if there are multiple remotes" {
skiponwindows "Missing dependencies"
cd repo1
dolt checkout -b feature
dolt commit -am "first commit"
dolt push remote1 feature
dolt checkout main
dolt push remote1 main
cd ../repo2
dolt fetch
dolt remote add remote2 file://../rem1
dolt fetch remote2
run dolt branch
[[ ! "$output" =~ "feature" ]] || false
start_sql_server repo2 >> server_log.txt 2>&1
# No data on main
run dolt sql-client --use-db repo2 -P $PORT -u dolt -q "show tables"
[ $status -eq 0 ]
[ "$output" = "" ]
run dolt sql-client --use-db repo2/feature -P $PORT -u dolt -q "select active_branch()"
[ $status -eq 1 ]
[[ "$output" =~ "database not found: repo2/feature" ]] || false
run grep "'feature' matched multiple remote tracking branches" server_log.txt
[ "${#lines[@]}" -ne 0 ]
}
get_head_commit() {
dolt log -n 1 | grep -m 1 commit | cut -c 13-44
}
+4 -4
View File
@@ -67,8 +67,8 @@ SQL
[ "$status" -eq "0" ]
[[ "$output" =~ "type,name,fragment,id" ]] || false
[[ "$output" =~ "trigger,trigger1,CREATE TRIGGER trigger1 BEFORE INSERT ON test FOR EACH ROW SET new.v1 = -new.v1,1" ]] || false
[[ "$output" =~ "view,view1,SELECT v1 FROM test,2" ]] || false
[[ "$output" =~ "view,view2,SELECT y FROM b,3" ]] || false
[[ "$output" =~ "view,view1,CREATE VIEW view1 AS SELECT v1 FROM test,2" ]] || false
[[ "$output" =~ "view,view2,CREATE VIEW view2 AS SELECT y FROM b,3" ]] || false
[[ "$output" =~ "trigger,trigger2,CREATE TRIGGER trigger2 AFTER INSERT ON a FOR EACH ROW INSERT INTO b VALUES (new.x * 2),4" ]] || false
[[ "${#lines[@]}" = "5" ]] || false
}
@@ -214,8 +214,8 @@ SQL
run dolt sql -q "SELECT * FROM dolt_schemas" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "type,name,fragment,id" ]] || false
[[ "$output" =~ "view,view1,SELECT 2+2 FROM dual,1" ]] || false
[[ "$output" =~ "view,view2,SELECT 3+3 FROM dual,2" ]] || false
[[ "$output" =~ "view,view1,CREATE VIEW view1 AS SELECT 2+2 FROM dual,1" ]] || false
[[ "$output" =~ "view,view2,CREATE VIEW view2 AS SELECT 3+3 FROM dual,2" ]] || false
[[ "${#lines[@]}" = "3" ]] || false
run dolt sql -q "SELECT * FROM view1" -r=csv
+1 -1
View File
@@ -65,7 +65,7 @@ function test_backward_compatibility() {
PATH="`pwd`"/"$bin":"$PATH" setup_repo "$ver"
echo "Run the bats tests with current Dolt version hitting repositories from older Dolt version $ver"
DEFAULT_BRANCH="$DEFAULT_BRANCH" REPO_DIR="`pwd`"/repos/"$ver" bats ./test_files/bats
DEFAULT_BRANCH="$DEFAULT_BRANCH" REPO_DIR="`pwd`"/repos/"$ver" DOLT_VERSION="$ver" bats ./test_files/bats
}
function list_forward_compatible_versions() {
@@ -207,11 +207,23 @@ EOF
}
@test "dolt_schemas" {
run dolt sql -q "select * from dolt_schemas"
[ "$status" -eq 0 ]
[[ "${lines[1]}" =~ "| type | name | fragment |" ]] || false
[[ "${lines[2]}" =~ "+------+-------+----------------------+" ]] || false
[[ "${lines[3]}" =~ "| view | view1 | SELECT 2+2 FROM dual |" ]] || false
dolt_version=$( echo $DOLT_VERSION | sed -e "s/^v//" )
echo $dolt_version
if [[ ! -z $dolt_version ]]; then
run dolt sql -q "select * from dolt_schemas"
[ "$status" -eq 0 ]
[[ "${lines[1]}" =~ "| type | name | fragment |" ]] || false
[[ "${lines[2]}" =~ "+------+-------+----------------------+" ]] || false
[[ "${lines[3]}" =~ "| view | view1 | SELECT 2+2 FROM dual |" ]] || false
else
run dolt sql -q "select * from dolt_schemas"
[ "$status" -eq 0 ]
[[ "${lines[1]}" =~ "| type | name | fragment |" ]] || false
[[ "${lines[2]}" =~ "+------+-------+-------------------------------------------+" ]] || false
[[ "${lines[3]}" =~ "| view | view1 | CREATE VIEW view1 AS SELECT 2+2 FROM dual |" ]] || false
fi
run dolt sql -q 'select * from view1'
[ "$status" -eq 0 ]
[[ "${lines[1]}" =~ "2+2" ]] || false
@@ -2,8 +2,11 @@
We created tests for loading data dumps from mysqldump, and we run these tests through Github Actions
on pull requests.
These tests can be run locally using Docker. From the root directory of this repo, run:
These tests can be run locally using Docker. Before you can build the image, you also need to copy the go folder
into the integration-tests folder; unfortunately just symlinking doesn't seem to work. From the
integration-tests directory of the dolt repo, run:
```bash
$ cp -r ../go .
$ docker build -t data-dump-loading-tests -f DataDumpLoadDockerfile .
$ docker run data-dump-loading-tests:latest
```

Some files were not shown because too many files have changed in this diff Show More