Files
fathom/internal/store/read.go
zzgandClaude Opus 5 79fe343be5 Move the module path to git.z.gd/zzg/fathom
The module was declared as github.com/zzg/fathom, which is not where
this repo lives and never has been. Nothing is published at that path,
so `go get github.com/zzg/fathom` resolves to a 404 while the import
paths in every file assert otherwise.

git.z.gd is a real module path rather than a cosmetic correction:
Gitea serves the go-import meta tag for it,

  <meta name="go-import" content="git.z.gd/zzg/fathom git https://git.z.gd/zzg/fathom.git">

and the repo answers unauthenticated over HTTPS, so `go get
git.z.gd/zzg/fathom` resolves where the old path could not.

Mechanical: the module line plus 46 import paths across 23 files. There
is no go.sum to churn, since fathom has no third-party dependencies.
go build, go vet, gofmt and the full test suite are clean under the new
path, and the built binary still runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 20:31:18 -07:00

97 lines
2.9 KiB
Go

package store
import (
"git.z.gd/zzg/fathom/internal/model"
)
// The exported read surface. Scans go through these rather than through the
// query engine when the caller wants whole records — the trace list and the
// trace detail view both do, since neither is expressible as an aggregate.
// ReadSpan materialises row i of a chunk as a Span, without its blobs.
func ReadSpan(c *Chunk, i int) model.Span { return readSpan(c, i) }
// ReadLog materialises row i of a chunk as a LogRecord, without its body.
func ReadLog(c *Chunk, i int) model.LogRecord { return readLog(c, i) }
// InputRef, OutputRef, and BodyRef return a row's blob references so callers
// can defer the read until someone actually looks at the content.
func InputRef(c *Chunk, i int) BlobRef { return blobAt(c, ColInput, i) }
func OutputRef(c *Chunk, i int) BlobRef { return blobAt(c, ColOutput, i) }
func BodyRef(c *Chunk, i int) BlobRef { return blobAt(c, ColBody, i) }
// ScanSpans visits every span in a time range, oldest shard first.
//
// The callback receives the shard so it can resolve blob references lazily. It
// returns false to stop early, which is what lets a bounded trace lookup avoid
// reading the rest of the range once it has what it came for.
func (s *Store) ScanSpans(minTS, maxTS int64, fn func(sp model.Span, sh *Shard, c *Chunk, i int) bool) (Scanned, error) {
return s.scan(model.TableSpans, minTS, maxTS, func(sh *Shard, c *Chunk, i int) bool {
sp := readSpan(c, i)
if sp.StartTS < minTS || sp.StartTS > maxTS {
return true
}
return fn(sp, sh, c, i)
})
}
// ScanLogs visits every log record in a time range.
func (s *Store) ScanLogs(minTS, maxTS int64, fn func(rec model.LogRecord, sh *Shard, c *Chunk, i int) bool) (Scanned, error) {
return s.scan(model.TableLogs, minTS, maxTS, func(sh *Shard, c *Chunk, i int) bool {
rec := readLog(c, i)
if rec.TS < minTS || rec.TS > maxTS {
return true
}
return fn(rec, sh, c, i)
})
}
// Scanned reports how much a scan read, so callers can show the cost honestly
// rather than implying an index did the work.
type Scanned struct {
Shards int
Chunks int
Rows int
}
var errStopScan = stopScan{}
type stopScan struct{}
func (stopScan) Error() string { return "scan stopped" }
func (s *Store) scan(table string, minTS, maxTS int64, fn func(*Shard, *Chunk, int) bool) (Scanned, error) {
var sc Scanned
shards, err := s.ShardsOverlapping(table, minTS, maxTS)
if err != nil {
return sc, err
}
sc.Shards = len(shards)
for _, info := range shards {
sh, err := s.OpenShardForRead(info.Table, info.Date)
if err != nil {
// One unreadable shard should cost that shard, not the query.
continue
}
err = sh.Chunks(func(c *Chunk) error {
sc.Chunks++
sc.Rows += c.Rows
for i := 0; i < c.Rows; i++ {
if !fn(sh, c, i) {
return errStopScan
}
}
return nil
})
if err == errStopScan {
return sc, nil
}
if err != nil {
continue
}
}
return sc, nil
}