G §57, §345: a replace leaves the object it replaced, and a value over a bundle's object limit is refused, never split
Builds: design 47 G §57, §345 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
+17
-80
@@ -43,7 +43,7 @@ type Object struct {
|
||||
|
||||
// Objects is the object store beside the state.
|
||||
type Objects interface {
|
||||
// MostBytes is the largest object it takes in one put, or zero for no limit: a value over it is split over objects.
|
||||
// MostBytes is the largest object it takes in one put, or zero for no limit: a value over it is refused, loudly.
|
||||
MostBytes() int
|
||||
Put(name string, data []byte) error
|
||||
Get(name string) ([]byte, error)
|
||||
@@ -79,8 +79,9 @@ func digestOf(data []byte) string {
|
||||
func objectName(key, digest string) string { return key + "." + digest }
|
||||
|
||||
// Put writes the value as JSON under key: inline up to the limit, else as an object `<key>.<sha256>` with its reference
|
||||
// in the key. A replace removes the object it replaced in the same act; a write another writer won first is ErrChanged
|
||||
// and leaves what it found. A value that is itself exactly a reference is refused.
|
||||
// in the key. A replace leaves the object it replaced, as a past value may still name it (ADR 0595 §4): it is listed
|
||||
// key-gone until the drop tool removes it on a person's word. A write another writer won first is ErrChanged and leaves
|
||||
// what it found. A value that is itself exactly a reference is refused.
|
||||
func (v Values) Put(key string, value any) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
@@ -99,21 +100,18 @@ func (v Values) Put(key string, value any) error {
|
||||
oldRef, hadRef = ReadRef(old.Value)
|
||||
}
|
||||
body, object := data, ""
|
||||
var newRef Ref
|
||||
if len(data) > v.inlineMost() {
|
||||
ref := Ref{Store: v.Store, Object: objectName(key, digestOf(data)), Size: int64(len(data)), Digest: digestOf(data), Form: FormVersion}
|
||||
object = ref.Object
|
||||
if most := v.Objects.MostBytes(); most > 0 && len(data) > most {
|
||||
return fmt.Errorf("the value for %s is %d bytes, over the %d an object may hold through this runtime: it is refused", key, len(data), most)
|
||||
}
|
||||
if !(hadRef && oldRef.Object == object) {
|
||||
parts, err := putObject(v.Objects, object, data)
|
||||
if err != nil {
|
||||
if err := v.Objects.Put(object, data); err != nil {
|
||||
return fmt.Errorf("the object for %s: %w", key, err)
|
||||
}
|
||||
ref.Parts = parts
|
||||
} else {
|
||||
ref.Parts = oldRef.Parts
|
||||
}
|
||||
body = Wrap(ref)
|
||||
newRef = ref
|
||||
}
|
||||
if old == nil {
|
||||
err = v.KV.Create(key, body)
|
||||
@@ -122,15 +120,11 @@ func (v Values) Put(key string, value any) error {
|
||||
}
|
||||
if err != nil {
|
||||
if object != "" && !(hadRef && oldRef.Object == object) {
|
||||
_ = deleteObject(v.Objects, newRef)
|
||||
// an object no key and no past value names: made for this write alone
|
||||
_ = v.Objects.Delete(object)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if hadRef && oldRef.Object != object {
|
||||
if err := deleteObject(v.Objects, oldRef); err != nil {
|
||||
return fmt.Errorf("%s is written; the object it replaced, %s, could not be removed: %w", key, oldRef.Object, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,7 +137,7 @@ func (v Values) Get(key string, into any) (bool, error) {
|
||||
}
|
||||
data := e.Value
|
||||
if ref, ok := ReadRef(e.Value); ok {
|
||||
if data, err = getObject(v.Objects, ref); err != nil {
|
||||
if data, err = v.Objects.Get(ref.Object); err != nil {
|
||||
return false, fmt.Errorf("the object %s of %s: %w", ref.Object, key, err)
|
||||
}
|
||||
if int64(len(data)) != ref.Size || digestOf(data) != ref.Digest {
|
||||
@@ -209,9 +203,7 @@ func (v Values) live() (map[string]string, error) {
|
||||
continue
|
||||
}
|
||||
if ref, ok := ReadRef(e.Value); ok {
|
||||
for _, name := range ref.names() {
|
||||
live[name] = key
|
||||
}
|
||||
live[ref.Object] = key
|
||||
}
|
||||
}
|
||||
return live, nil
|
||||
@@ -237,67 +229,12 @@ func (v Values) DropKeyGone() ([]string, error) {
|
||||
return dropped, nil
|
||||
}
|
||||
|
||||
// names are the objects a reference stands for.
|
||||
func (r Ref) names() []string {
|
||||
if r.Parts <= 1 {
|
||||
return []string{r.Object}
|
||||
}
|
||||
out := make([]string, r.Parts)
|
||||
for i := range out {
|
||||
out[i] = fmt.Sprintf("%s.%d", r.Object, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// keyOf is the key an object `<key>.<digest>` or `<key>.<digest>.<n>` was written for.
|
||||
// keyOf is the key an object `<key>.<digest>` was written for.
|
||||
func keyOf(object string) string {
|
||||
isDigest := func(s string) bool { _, err := hex.DecodeString(s); return len(s) == 64 && err == nil }
|
||||
parts := strings.Split(object, ".")
|
||||
if n := len(parts); n >= 3 && isDigest(parts[n-2]) {
|
||||
return strings.Join(parts[:n-2], ".")
|
||||
}
|
||||
if n := len(parts); n >= 2 && isDigest(parts[n-1]) {
|
||||
return strings.Join(parts[:n-1], ".")
|
||||
if i := strings.LastIndex(object, "."); i > 0 {
|
||||
if _, err := hex.DecodeString(object[i+1:]); err == nil && len(object)-i-1 == 64 {
|
||||
return object[:i]
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
// putObject stores data as one object, or over `<name>.<n>` when the store takes none so large; it answers the parts
|
||||
// (zero for one object) and removes what it stored when a part fails.
|
||||
func putObject(o Objects, name string, data []byte) (int, error) {
|
||||
most := o.MostBytes()
|
||||
if most <= 0 || len(data) <= most {
|
||||
return 0, o.Put(name, data)
|
||||
}
|
||||
ref := Ref{Object: name, Parts: (len(data) + most - 1) / most}
|
||||
for i, part := range ref.names() {
|
||||
from, to := i*most, min((i+1)*most, len(data))
|
||||
if err := o.Put(part, data[from:to]); err != nil {
|
||||
_ = deleteObject(o, Ref{Object: name, Parts: i})
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return ref.Parts, nil
|
||||
}
|
||||
|
||||
func getObject(o Objects, ref Ref) ([]byte, error) {
|
||||
var out []byte
|
||||
for _, name := range ref.names() {
|
||||
d, err := o.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, d...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func deleteObject(o Objects, ref Ref) error {
|
||||
var first error
|
||||
for _, name := range ref.names() {
|
||||
if err := o.Delete(name); err != nil && first == nil {
|
||||
first = err
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user