Artifact 9509db183db12c339a953082dfe99d3227b835b5137e36a7938e3d1a512bd484:
- File
internal/content/cone_reference_metadata_test.go
— part of check-in
[0fc4d176c3]
at
2026-10-28 22:52:43
on branch feat/chunked-blobs
— Record chunked length in blob.content and verify parts on read
Review of the chunked-blob change found a missing last part read back short with no error, and a chunked write outside a transaction could leave a torn row that looked held.
A chunked row now keeps its stored length in blob.content as an INTEGER. Reads check the parts run unbroken from seq 0, are full-sized but for the last, and add up to that length; anything else is ErrPartsCorrupt, and a NULL content is no longer mistaken for a chunked one. StoredLenSQL reads the length off the row and needs no schema probe.
Chunked writes, and the phantom fill around them, run in a savepoint of the caller's transaction or a transaction of their own, so a failed part leaves nothing behind. A value needing more than 4096 parts is an error, not a panic, since stored values can come from peers. The cone and sparse local payload reads use ReadStoredExact, which loads a value only at the length already budgeted for.
MaxValueBytes is documented as process-wide, and unversioned content as not chunked. (user: [email protected], size: 1915) [annotate] [blame] [check-ins using]
package content import ( "database/sql" "math/rand" "strings" "testing" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/blob" ) // length(BLOB) and typeof use SQLite's metadata-only OP_Column flags. A CAST // or substr in this first pass removes that optimization and materializes the // entire content value before its stored-byte budget has been proved. The // one plain read is the chunked arm, CASE typeof(content) WHEN 'integer', // which only runs on a row whose content is an 8-byte INTEGER length. func TestConeReferenceMetadataDoesNotMaterializeContent(t *testing.T) { d := setupTestDB(t) assertMetadataLazy(t, d) t.Cleanup(blob.SetMaxValueBytes(1_990_000)) big := make([]byte, 2_100_000) _, _ = rand.New(rand.NewSource(1)).Read(big) if _, _, err := blob.Store(d, big); err != nil { t.Fatal(err) } assertMetadataLazy(t, d) } func assertMetadataLazy(t *testing.T, d *db.DB) { t.Helper() var cid int if err := d.QueryRow("SELECT cid FROM pragma_table_info('blob') WHERE name='content'").Scan(&cid); err != nil { t.Fatal(err) } rows, err := d.Query("EXPLAIN " + referenceMetadataSQL) if err != nil { t.Fatal(err) } defer rows.Close() columns, plain := 0, 0 for rows.Next() { var addr, p1, p2, p3, p5 int var opcode string var p4, comment sql.NullString if err := rows.Scan(&addr, &opcode, &p1, &p2, &p3, &p4, &p5, &comment); err != nil { t.Fatal(err) } if opcode == "Column" && p2 == cid { columns++ // SQLite OPFLAG_LENGTHARG=0x40, OPFLAG_TYPEOFARG=0x80. if p5&0xc0 == 0 { plain++ } } } if err := rows.Err(); err != nil { t.Fatal(err) } if columns == 0 { t.Fatal("did not inspect any content-column reads") } if plain != 1 || !strings.Contains(referenceMetadataSQL, "WHEN 'integer' THEN b.content") { t.Fatalf("preflight reads content plainly %d times, want only the integer arm", plain) } }