Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

210 lines
5.5 KiB
Go

// Copyright 2021 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.
package commands
import (
"context"
"fmt"
"io"
"path/filepath"
"sort"
"time"
"github.com/fatih/color"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/gen/fb/serial"
"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/dolthub/dolt/go/store/nbs"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/dolt/go/store/spec"
"github.com/dolthub/dolt/go/store/types"
)
const numFilesParam = "numfiles"
type RootsCmd struct {
}
// Name is returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
func (cmd RootsCmd) Name() string {
return "roots"
}
// Hidden should return true if this command should be hidden from the help text
func (cmd RootsCmd) Hidden() bool {
return true
}
// Description returns a description of the command
func (cmd RootsCmd) Description() string {
return "Displays store root values (or potential store root values) that we find in the current database."
}
func (cmd RootsCmd) Docs() *cli.CommandDocumentation {
return nil
}
func (cmd RootsCmd) ArgParser() *argparser.ArgParser {
ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 0)
ap.SupportsInt(numFilesParam, "n", "number", "Number of table files to scan.")
return ap
}
// Exec executes the command
func (cmd RootsCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
ap := cmd.ArgParser()
help, _ := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, cli.CommandDocumentationContent{}, ap))
apr := cli.ParseArgsOrDie(ap, args, help)
doltDir := dEnv.GetDoltDir()
if doltDir == "" {
cli.Println(color.YellowString("can no longer find a database on disk"))
return 1
}
dir := filepath.Join(doltDir, dbfactory.DataDir)
oldgen := filepath.Join(dir, "oldgen")
itr, err := NewTableFileIter([]string{dir, oldgen}, dEnv.FS)
if err != nil {
return BuildVerrAndExit("Unable to read table files.", err)
}
n := apr.GetIntOrDefault(numFilesParam, len(itr.files))
for i := 0; i < n; i++ {
fPath, modified := itr.next()
err = cmd.processTableFile(ctx, fPath, modified, dEnv.FS)
if err == io.EOF {
break
} else if err != nil {
cli.Println(color.YellowString("Failed to process '%s'. Cause: %v", fPath, err))
}
}
return 0
}
func (cmd RootsCmd) processTableFile(ctx context.Context, path string, modified time.Time, fs filesys.Filesys) error {
cli.Printf("Processing '%s' last modified: %v\n", path, modified)
rdCloser, err := fs.OpenForRead(path)
if err != nil {
return err
}
defer rdCloser.Close()
return nbs.IterChunks(ctx, rdCloser.(io.ReadSeeker), func(chunk chunks.Chunk) (stop bool, err error) {
// Want a clean db every loop
sp, _ := spec.ForDatabase("mem")
vrw := sp.GetVRW(ctx)
value, err := types.DecodeValue(chunk, vrw)
if err != nil {
return false, err
}
if sm, ok := value.(types.SerialMessage); ok {
if serial.GetFileID(sm) == serial.StoreRootFileID {
msg, err := serial.TryGetRootAsStoreRoot([]byte(sm), serial.MessagePrefixSz)
if err != nil {
return false, err
}
ambytes := msg.AddressMapBytes()
node, fileId, err := tree.NodeFromBytes(ambytes)
if err != nil {
return false, err
}
if fileId != serial.AddressMapFileID {
return false, fmt.Errorf("unexpected file ID for address map, expected %s, found %s", serial.AddressMapFileID, fileId)
}
err = tree.OutputAddressMapNode(cli.OutStream, node)
if err != nil {
return false, err
}
cli.Println()
}
} else {
return false, fmt.Errorf("unexpected value type found in table file: %T", value)
}
return false, nil
})
}
type fileAndTime struct {
Path string
Modified time.Time
}
type TableFileIter struct {
files []fileAndTime
pos int
}
func NewTableFileIter(dirs []string, fs filesys.Filesys) (*TableFileIter, error) {
var tableFiles []fileAndTime
for _, dir := range dirs {
err := fs.Iter(dir, false, func(path string, size int64, isDir bool) (stop bool) {
if !isDir {
filename := filepath.Base(path)
if len(filename) == 32 {
t, ok := fs.LastModified(path)
if !ok {
t = time.Now()
}
tableFiles = append(tableFiles, fileAndTime{path, t})
}
}
return false
})
if err != nil {
return nil, err
}
}
if len(tableFiles) == 0 {
return nil, fmt.Errorf("No table files found in '%v'", dirs)
}
sort.Slice(tableFiles, func(i, j int) bool {
return tableFiles[i].Modified.Sub(tableFiles[j].Modified) > 0
})
return &TableFileIter{tableFiles, 0}, nil
}
func (itr *TableFileIter) next() (string, time.Time) {
if itr.pos >= len(itr.files) {
return "", time.Time{}
}
curr := itr.files[itr.pos]
itr.pos++
return curr.Path, curr.Modified
}