项目文件夹

文件
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

280 行
7.7 KiB
Go

// Copyright 2024 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 nbs
import (
"context"
"fmt"
"io"
"path/filepath"
"strings"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
dherrors "github.com/dolthub/dolt/go/libraries/utils/errors"
"github.com/dolthub/dolt/go/store/chunks"
"github.com/dolthub/dolt/go/store/hash"
)
type archiveChunkSource struct {
aRdr archiveReader
file string
refs refCounter
}
var _ chunkSource = &archiveChunkSource{}
func newArchiveChunkSource(ctx context.Context, dir string, h hash.Hash, chunkCount uint32, q MemoryQuotaProvider, mmapArchiveIndexes bool, refs refCounter, stats *Stats) (*archiveChunkSource, error) {
archiveFile := filepath.Join(dir, h.String()+ArchiveFileSuffix)
fra, err := newFileReaderAt(archiveFile, mmapArchiveIndexes)
if err != nil {
return nil, err
}
aRdr, err := newArchiveReader(ctx, fra, h, uint64(fra.sz), q, stats)
if err != nil {
return nil, err
}
return &archiveChunkSource{aRdr: aRdr, file: archiveFile, refs: refs}, nil
}
func newAWSArchiveChunkSource(ctx context.Context,
s3 *s3ObjectReader,
al awsLimits,
name string,
chunkCount uint32,
q MemoryQuotaProvider,
stats *Stats) (cs chunkSource, err error) {
footer, err := q.AcquireQuotaByteSlice(ctx, int(archiveFooterSize))
if err != nil {
return emptyChunkSource{}, err
}
defer q.ReleaseQuotaBytes(int(archiveFooterSize))
// sz is what we are really after here, but we'll use the bytes to construct the footer to avoid another call.
_, sz, err := s3.readS3ObjectFromEnd(ctx, name, footer, stats)
if err != nil {
return emptyChunkSource{}, err
}
id := strings.TrimSuffix(filepath.Base(name), ArchiveFileSuffix)
hashId, ok := hash.MaybeParse(id)
if !ok {
return emptyChunkSource{}, fmt.Errorf("invalid archive file path: %s", name)
}
aRdr, err := newArchiveReaderFromFooter(ctx, &s3TableReaderAt{s3, name}, hashId, sz, footer, q, stats)
if err != nil {
return emptyChunkSource{}, err
}
return &archiveChunkSource{aRdr: aRdr, refs: noopRefCounter{}}, nil
}
func (acs *archiveChunkSource) has(h hash.Hash, keeper keeperF) (bool, gcBehavior, error) {
res := acs.aRdr.has(h)
if res && keeper != nil && keeper(h) {
return false, gcBehavior_Block, nil
}
return res, gcBehavior_Continue, nil
}
func (acs *archiveChunkSource) hasMany(records []hasRecord, keeper keeperF) (bool, gcBehavior, error) {
// single threaded first pass.
foundAll := true
for i, req := range records {
if req.has {
continue
}
h := *req.a
if acs.aRdr.has(h) {
if keeper != nil && keeper(h) {
return false, gcBehavior_Block, nil
}
records[i].has = true
} else {
foundAll = false
}
}
return !foundAll, gcBehavior_Continue, nil
}
func (acs *archiveChunkSource) get(ctx context.Context, h hash.Hash, keeper keeperF, stats *Stats) ([]byte, gcBehavior, error) {
res, err := acs.aRdr.get(ctx, h, stats)
if err != nil {
return nil, gcBehavior_Continue, err
}
if res != nil && keeper != nil && keeper(h) {
return nil, gcBehavior_Block, nil
}
return res, gcBehavior_Continue, nil
}
func (acs *archiveChunkSource) getMany(ctx context.Context, eg *errgroup.Group, records []getRecord, found func(context.Context, *chunks.Chunk), keeper keeperF, stats *Stats) (bool, gcBehavior, error) {
// single threaded first pass.
foundAll := true
for i, req := range records {
if req.found {
continue
}
h := *req.a
data, err := acs.aRdr.get(ctx, h, stats)
if err != nil {
return true, gcBehavior_Continue, err
}
if data == nil {
foundAll = false
} else {
if keeper != nil && keeper(h) {
return true, gcBehavior_Block, nil
}
chunk := chunks.NewChunk(data)
found(ctx, &chunk)
records[i].found = true
}
}
return !foundAll, gcBehavior_Continue, nil
}
// iterate iterates over the archive chunks. The callback is called for each chunk in the archive. This is not optimized
// as currently is it only used for un-archiving, which should be uncommon.
func (acs *archiveChunkSource) iterate(ctx context.Context, cb func(chunks.Chunk) error, stats *Stats) error {
return acs.aRdr.iterate(ctx, cb, stats)
}
func (acs *archiveChunkSource) count() uint32 {
return acs.aRdr.count()
}
func (acs *archiveChunkSource) close() error {
err := acs.aRdr.close()
acs.refs.decRef()
return err
}
func (acs *archiveChunkSource) hash() hash.Hash {
return acs.aRdr.footer.hash
}
func (acs *archiveChunkSource) suffix() string {
return ArchiveFileSuffix
}
func (acs *archiveChunkSource) currentSize() uint64 {
return acs.aRdr.footer.fileSize
}
// reader returns a reader for the entire archive file.
func (acs *archiveChunkSource) reader(ctx context.Context, _ dherrors.FatalBehavior) (io.ReadCloser, uint64, error) {
rd, err := acs.aRdr.reader.Reader(ctx)
if err != nil {
return nil, 0, err
}
return rd, acs.currentSize(), nil
}
func (acs *archiveChunkSource) uncompressedLen() (uint64, error) {
return 0, errors.New("Archive chunk source does not support uncompressedLen")
}
func (acs *archiveChunkSource) index() (tableIndex, error) {
return nil, errors.New("Archive chunk source does not expose table file indexes")
}
func (acs *archiveChunkSource) clone() (chunkSource, error) {
reader, err := acs.aRdr.clone()
if err != nil {
return nil, err
}
acs.refs.addRef()
return &archiveChunkSource{
aRdr: reader,
file: acs.file,
refs: acs.refs,
}, nil
}
func (acs *archiveChunkSource) getRecordRanges(_ context.Context, _ dherrors.FatalBehavior, records []getRecord, keeper keeperF) (map[hash.Hash]Range, gcBehavior, error) {
result := make(map[hash.Hash]Range, len(records))
for i, req := range records {
if req.found {
continue
}
hAddr := *req.a
idx := acs.aRdr.search(hAddr)
if idx < 0 {
// Chunk not found.
continue
}
records[i].found = true
if keeper != nil && keeper(hAddr) {
return nil, gcBehavior_Block, nil
}
dictId, dataId := acs.aRdr.getChunkRef(idx)
dataSpan := acs.aRdr.getByteSpanByID(dataId)
dictSpan := acs.aRdr.getByteSpanByID(dictId)
rng := Range{
Offset: dataSpan.offset,
Length: uint32(dataSpan.length),
DictOffset: dictSpan.offset,
DictLength: uint32(dictSpan.length),
}
result[hAddr] = rng
}
return result, gcBehavior_Continue, nil
}
func (acs *archiveChunkSource) getManyCompressed(ctx context.Context, eg *errgroup.Group, reqs []getRecord, found func(context.Context, ToChunker), keeper keeperF, stats *Stats) (bool, gcBehavior, error) {
foundAll := true
for i, req := range reqs {
if req.found {
continue
}
h := *req.a
toChk, err := acs.aRdr.getAsToChunker(ctx, h, stats)
if err != nil {
return true, gcBehavior_Continue, err
}
if toChk == nil || toChk.IsEmpty() {
foundAll = false
} else {
if keeper != nil && keeper(h) {
return true, gcBehavior_Block, nil
}
found(ctx, toChk)
reqs[i].found = true
}
}
return !foundAll, gcBehavior_Continue, nil
}
func (acs *archiveChunkSource) iterateAllChunks(ctx context.Context, cb func(chunks.Chunk), stats *Stats) error {
ncb := func(c chunks.Chunk) error {
cb(c)
return nil
}
return acs.aRdr.iterate(ctx, ncb, stats)
}
func (acs *archiveChunkSource) tolerantIterateAllChunks(ctx context.Context, cb func(chunks.Chunk), errCb func(error), stats *Stats) {
acs.aRdr.tolerantIterate(ctx, cb, errCb, stats)
}