Artifact 6b7af015b45471e78b56399e277a03926c60dd9d4bf8dc894c86dc2b4b9f6dc3:
- File
internal/content/sparse.go
— part of check-in
[524c749185]
at
2026-10-20 00:25:58
on branch feat/submanifest-reader
— feat(content): store cone and the three absence classes of the sparse store
The store cone (draft-fossil-sparse-cone-00 §5) is kept in a storecone table that exists only while a cone is set. fossil rebuild drops tables it does not know, so losing the cone reads as dense and the store heals toward dense (§5.2). A cone that does not parse also reads as dense.
LoadAbsences sorts every phantom into transit, shunned or elided by zit's rules. A phantom is elided when F cards name it only outside the store cone and no held delta uses it as a base. (user: [email protected], size: 7218) [annotate] [blame] [check-ins using]
package content import ( "database/sql" "errors" "fmt" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/cone" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" ) // The store cone of draft-fossil-sparse-cone-00 §5 lives in a table of its // own, created when a cone is set and dropped when it is cleared, so a dense // repository's schema is untouched. `fossil rebuild` drops every table it // does not know, which puts the cone in the disposable tier §5.2 recommends: // losing it reclassifies every elided phantom as a transit phantom, and the // store heals toward dense through ordinary synchronization. const storeConeSchema = `CREATE TABLE IF NOT EXISTS storecone( id INTEGER PRIMARY KEY CHECK(id=1), cone BLOB NOT NULL )` // StoreCone returns the store cone, or nil when the store is dense. A cone // this implementation cannot read -- a later serialization version, or bytes // that do not parse -- is policy loss (§3.1, §5.2) and reads as dense, never // as an error: the cost is bandwidth, not data. func StoreCone(q db.Querier) (*cone.Cone, error) { if q == nil { panic("content.StoreCone: q must not be nil") } var n int if err := q.QueryRow( "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='storecone'", ).Scan(&n); err != nil { return nil, fmt.Errorf("content.StoreCone: %w", err) } if n == 0 { return nil, nil } var data []byte err := q.QueryRow("SELECT cone FROM storecone WHERE id=1").Scan(&data) if errors.Is(err, sql.ErrNoRows) { return nil, nil } if err != nil { return nil, fmt.Errorf("content.StoreCone: %w", err) } c, err := cone.Parse(data) if err != nil { return nil, nil } return c, nil } // SetStoreCone replaces the store cone; nil makes the store dense. Neither // direction moves content: narrowing deletes nothing (§5.4), and widening // only makes the newly covered elided phantoms wanted again, for the next // synchronization to fetch. func SetStoreCone(q db.Querier, c *cone.Cone) error { if q == nil { panic("content.SetStoreCone: q must not be nil") } if c == nil { if _, err := q.Exec("DROP TABLE IF EXISTS storecone"); err != nil { return fmt.Errorf("content.SetStoreCone: %w", err) } return nil } if _, err := q.Exec(storeConeSchema); err != nil { return fmt.Errorf("content.SetStoreCone: %w", err) } if _, err := q.Exec("REPLACE INTO storecone(id, cone) VALUES(1, ?)", c.Serialize()); err != nil { return fmt.Errorf("content.SetStoreCone: %w", err) } return nil } // Absence classifies an artifact the store names: held, or absent for one // of the three reasons of §5.2. type Absence int const ( // Held is not absent: the store holds the content. Held Absence = iota // Transit is wanted and not yet arrived; synchronization requests it. Transit // Shunned is forbidden and never requested. Shunned // Elided is deliberately absent under the store cone. Synchronization // does not request it, and its absence is healthy. Elided ) func (a Absence) String() string { switch a { case Held: return "held" case Transit: return "transit" case Shunned: return "shunned" case Elided: return "elided" } return fmt.Sprintf("Absence(%d)", int(a)) } // Absences is the classification of every phantom in a store, computed from // the store cone at load time. Elision is never stored on a phantom (§5.2): // a cone change is a reload, not a migration. type Absences struct { cone *cone.Cone class map[libfossil.FslID]Absence counts [Elided + 1]int } // LoadAbsences classifies the store's phantoms. A phantom is shunned when its // name is in the shun table. Otherwise it is elided when the store has a // cone, some F card names it under a path the cone does not cover, no F card // names it under a covered path, and no held artifact is a delta over it; // anything else is in transit. These are zit's rules (src/sparse.zig): only a // file name proves an artifact out of cone, so a manifest, a sub-manifest // node or a control artifact is never elided, and the store holds them // densely (§5.1). F-card references are read from mlink, which records every // (name, content) pair a check-in introduces, a rename's prior name included. func LoadAbsences(q db.Querier) (*Absences, error) { if q == nil { panic("content.LoadAbsences: q must not be nil") } c, err := StoreCone(q) if err != nil { return nil, err } a := &Absences{cone: c, class: map[libfossil.FslID]Absence{}} rows, err := q.Query(` SELECT p.rid, EXISTS(SELECT 1 FROM shun WHERE uuid=b.uuid), EXISTS(SELECT 1 FROM delta d JOIN blob h ON h.rid=d.rid WHERE d.srcid=p.rid AND h.size>=0) FROM phantom p JOIN blob b ON b.rid=p.rid`) if err != nil { return nil, fmt.Errorf("content.LoadAbsences: phantoms: %w", err) } candidates := map[libfossil.FslID]bool{} for rows.Next() { var rid libfossil.FslID var shunned, base bool if err := rows.Scan(&rid, &shunned, &base); err != nil { rows.Close() return nil, fmt.Errorf("content.LoadAbsences: phantoms: %w", err) } switch { case shunned: a.class[rid] = Shunned default: a.class[rid] = Transit if c != nil && !base { candidates[rid] = true } } } if err := errors.Join(rows.Err(), rows.Close()); err != nil { return nil, fmt.Errorf("content.LoadAbsences: phantoms: %w", err) } if len(candidates) > 0 { if err := a.elide(q, candidates); err != nil { return nil, err } } for _, k := range a.class { a.counts[k]++ } return a, nil } // elide marks the candidates F cards name only outside the cone. func (a *Absences) elide(q db.Querier, candidates map[libfossil.FslID]bool) error { rows, err := q.Query(` SELECT m.fid, f.name FROM phantom p JOIN mlink m ON m.fid=p.rid JOIN filename f ON f.fnid=m.fnid UNION SELECT m.pid, f.name FROM phantom p JOIN mlink m ON m.pid=p.rid JOIN filename f ON f.fnid=CASE WHEN m.pfnid>0 THEN m.pfnid ELSE m.fnid END`) if err != nil { return fmt.Errorf("content.LoadAbsences: references: %w", err) } out := map[libfossil.FslID]bool{} in := map[libfossil.FslID]bool{} for rows.Next() { var rid libfossil.FslID var name string if err := rows.Scan(&rid, &name); err != nil { rows.Close() return fmt.Errorf("content.LoadAbsences: references: %w", err) } if !candidates[rid] { continue } if a.cone.Covers(name) { in[rid] = true } else { out[rid] = true } } if err := errors.Join(rows.Err(), rows.Close()); err != nil { return fmt.Errorf("content.LoadAbsences: references: %w", err) } for rid := range out { if !in[rid] { a.class[rid] = Elided } } return nil } // Cone is the store cone the classification used, nil for a dense store. func (a *Absences) Cone() *cone.Cone { return a.cone } // Class reports why rid is absent, or Held when it is not a phantom. func (a *Absences) Class(rid libfossil.FslID) Absence { if k, ok := a.class[rid]; ok { return k } return Held } // Count is the number of phantoms in class k. Held is not counted. func (a *Absences) Count(k Absence) int { if k == Held { panic("content.Absences.Count: held artifacts are not counted") } return a.counts[k] }