go-hep.org/x/hep@v0.38.1/groot/riofs/key.go (about) 1 // Copyright ©2017 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package riofs 6 7 import ( 8 "fmt" 9 "io" 10 "reflect" 11 "strings" 12 "time" 13 14 "go-hep.org/x/hep/groot/internal/rcompress" 15 "go-hep.org/x/hep/groot/rbytes" 16 "go-hep.org/x/hep/groot/root" 17 "go-hep.org/x/hep/groot/rtypes" 18 "go-hep.org/x/hep/groot/rvers" 19 ) 20 21 // KeyOption configures how a Key may be created. 22 type KeyOption func(cfg *keyCfg) error 23 24 // WithKeyCompression configures a Key to use the provided compression scheme. 25 func WithKeyCompression(compression int32) KeyOption { 26 return func(cfg *keyCfg) error { 27 cfg.compr = rcompress.SettingsFrom(compression) 28 return nil 29 } 30 } 31 32 type keyCfg struct { 33 compr rcompress.Settings 34 } 35 36 func newKeyCfg() *keyCfg { 37 return &keyCfg{ 38 compr: rcompress.Settings{Alg: rcompress.Inherit}, 39 } 40 } 41 42 // noKeyError is the error returned when a riofs.Key could not be found. 43 type noKeyError struct { 44 key string 45 obj root.Named 46 } 47 48 func (err noKeyError) Error() string { 49 return fmt.Sprintf("riofs: %s: could not find key %q", err.obj.Name(), err.key) 50 } 51 52 // keyTypeError is the error returned when a riofs.Key was found but the associated 53 // value is not of the expected type. 54 type keyTypeError struct { 55 key string 56 class string 57 } 58 59 func (err keyTypeError) Error() string { 60 return fmt.Sprintf("riofs: inconsistent value type for key %q (type=%s)", err.key, err.class) 61 } 62 63 // Key is a key (a label) in a ROOT file 64 // 65 // The Key class includes functions to book space on a file, 66 // to create I/O buffers, to fill these buffers 67 // to compress/uncompress data buffers. 68 // 69 // Before saving (making persistent) an object on a file, a key must 70 // be created. The key structure contains all the information to 71 // uniquely identify a persistent object on a file. 72 // The Key class is used by ROOT: 73 // - to write an object in the Current Directory 74 // - to write a new ntuple buffer 75 type Key struct { 76 f *File // underlying file 77 78 rvers int16 // version of the Key struct 79 nbytes int32 // number of bytes for the compressed object+key 80 objlen int32 // length of uncompressed object 81 datetime time.Time // Date/Time when the object was written 82 keylen int32 // number of bytes for the Key struct 83 cycle int16 // cycle number of the object 84 85 // address of the object on file (points to Key.bytes) 86 // this is a redundant information used to cross-check 87 // the data base integrity 88 seekkey int64 89 seekpdir int64 // pointer to the directory supporting this object 90 91 class string // object class name 92 name string // name of the object 93 title string // title of the object 94 95 left int32 // number of bytes left in current segment. 96 97 buf []byte // buffer of the Key's value 98 obj root.Object // Key's value 99 100 otyp reflect.Type // Go type of the Key's payload. 101 102 parent Directory // directory holding this key 103 } 104 105 func newKey(dir *tdirectoryFile, name, title, class string, objlen int32, f *File) Key { 106 k := Key{ 107 f: f, 108 rvers: rvers.Key, 109 objlen: objlen, 110 datetime: nowUTC(), 111 cycle: 1, 112 class: class, 113 name: name, 114 title: title, 115 seekpdir: f.begin, // FIXME(sbinet): see https://sft.its.cern.ch/jira/browse/ROOT-10352 116 parent: dir, 117 } 118 k.keylen = k.sizeof() 119 // FIXME(sbinet): this assumes the key-payload isn't compressed. 120 // if the key's payload is actually compressed, we introduce a hole 121 // with the f.setEnd call below. 122 k.nbytes = k.objlen + k.keylen 123 eof := f.end 124 if objlen > 0 { 125 k.seekkey = eof 126 err := f.setEnd(k.seekkey + int64(k.nbytes)) 127 if err != nil { 128 panic(err) 129 } 130 } 131 132 if eof > kStartBigFile { 133 k.rvers += 1000 134 } 135 136 if dir != nil { 137 k.seekpdir = dir.seekdir 138 } 139 140 return k 141 } 142 143 // NewKey creates a new key from the provided serialized object buffer. 144 // NewKey puts the key and its payload at the end of the provided file f. 145 // Depending on the file configuration, NewKey may compress the provided object buffer. 146 func NewKey(dir Directory, name, title, class string, cycle int16, obj []byte, f *File, kopts ...KeyOption) (Key, error) { 147 var d *tdirectoryFile 148 if dir != nil { 149 d = dir.(*tdirectoryFile) 150 } 151 return newKeyFromBuf(d, name, title, class, cycle, obj, f, kopts) 152 } 153 154 func newKeyFrom(dir *tdirectoryFile, name, title, class string, obj root.Object, f *File, kopts []KeyOption) (Key, error) { 155 var err error 156 if dir == nil { 157 dir = &f.dir 158 } 159 160 kcfg := newKeyCfg() 161 for _, opt := range kopts { 162 err = opt(kcfg) 163 if err != nil { 164 return Key{}, fmt.Errorf("riofs: could not setup Key option: %w", err) 165 } 166 } 167 168 keylen := keylenFor(name, title, class, dir, f.end) 169 170 buf := rbytes.NewWBuffer(nil, nil, uint32(keylen), dir.file) 171 switch obj := obj.(type) { 172 case rbytes.Marshaler: 173 _, err = obj.MarshalROOT(buf) 174 if err != nil { 175 return Key{}, fmt.Errorf("riofs: could not marshal object %T for key=%q: %w", obj, name, err) 176 } 177 default: 178 return Key{}, fmt.Errorf("riofs: object %T can not be ROOT serialized", obj) 179 } 180 181 objlen := int32(len(buf.Bytes())) 182 k := Key{ 183 f: f, 184 nbytes: keylen + objlen, 185 rvers: rvers.Key, 186 keylen: keylen, 187 objlen: objlen, 188 datetime: nowUTC(), 189 cycle: 1, 190 class: class, 191 name: name, 192 title: title, 193 seekkey: f.end, 194 seekpdir: dir.seekdir, 195 obj: obj, 196 otyp: reflect.TypeOf(obj), 197 parent: dir, 198 } 199 if f.IsBigFile() { 200 k.rvers += 1000 201 } 202 203 compress := k.f.compression 204 if kcfg.compr.Alg != rcompress.Inherit { 205 compress = kcfg.compr.Compression() 206 } 207 208 k.buf, err = rcompress.Compress(nil, buf.Bytes(), compress) 209 if err != nil { 210 return k, fmt.Errorf("riofs: could not compress object %T for key %q: %w", obj, name, err) 211 } 212 k.nbytes = k.keylen + int32(len(k.buf)) 213 214 err = f.setEnd(k.seekkey + int64(k.nbytes)) 215 if err != nil { 216 return k, fmt.Errorf("riofs: could not update ROOT file end: %w", err) 217 } 218 219 return k, nil 220 } 221 222 func newKeyFromBuf(dir *tdirectoryFile, name, title, class string, cycle int16, buf []byte, f *File, kopts []KeyOption) (Key, error) { 223 var err error 224 if dir == nil { 225 dir = &f.dir 226 } 227 228 kcfg := newKeyCfg() 229 for _, opt := range kopts { 230 err = opt(kcfg) 231 if err != nil { 232 return Key{}, fmt.Errorf("riofs: could not setup Key option: %w", err) 233 } 234 } 235 236 keylen := keylenFor(name, title, class, dir, f.end) 237 objlen := int32(len(buf)) 238 k := Key{ 239 f: f, 240 nbytes: keylen + objlen, 241 rvers: rvers.Key, 242 keylen: keylen, 243 objlen: objlen, 244 datetime: nowUTC(), 245 cycle: cycle, 246 class: class, 247 name: name, 248 title: title, 249 seekkey: f.end, 250 seekpdir: dir.seekdir, 251 parent: dir, 252 } 253 if f.IsBigFile() { 254 k.rvers += 1000 255 } 256 257 compress := k.f.compression 258 if kcfg.compr.Alg != rcompress.Inherit { 259 compress = kcfg.compr.Compression() 260 } 261 262 k.buf, err = rcompress.Compress(nil, buf, compress) 263 if err != nil { 264 return k, fmt.Errorf("riofs: could not compress object %s for key %q: %w", class, name, err) 265 } 266 k.nbytes = k.keylen + int32(len(k.buf)) 267 268 err = f.setEnd(k.seekkey + int64(k.nbytes)) 269 if err != nil { 270 return k, fmt.Errorf("riofs: could not update ROOT file end: %w", err) 271 } 272 273 return k, nil 274 } 275 276 // NewKeyForBasketInternal creates a new empty key. 277 // This is needed for Tree/Branch/Basket persistency. 278 // 279 // DO NOT USE. 280 func NewKeyForBasketInternal(dir Directory, name, title, class string, cycle int16) Key { 281 var ( 282 f = fileOf(dir) 283 d *tdirectoryFile 284 ) 285 switch v := dir.(type) { 286 case *File: 287 d = &v.dir 288 case *tdirectoryFile: 289 d = v 290 default: 291 panic(fmt.Errorf("riofs: invalid directory type %T", dir)) 292 } 293 294 k := Key{ 295 f: f, 296 rvers: rvers.Key, 297 cycle: cycle, 298 datetime: nowUTC(), 299 class: class, 300 name: name, 301 title: title, 302 seekpdir: f.begin, // FIXME(sbinet): see https://sft.its.cern.ch/jira/browse/ROOT-10352 303 parent: dir, 304 } 305 k.keylen = k.sizeof() 306 k.nbytes = k.keylen 307 if f.IsBigFile() { 308 k.rvers += 1000 309 } 310 311 if d != nil { 312 k.seekpdir = d.seekdir 313 } 314 315 return k 316 } 317 318 // KeyFromDir creates a new empty key (with no associated payload object) 319 // with provided name and title, and the expected object type name. 320 // The key will be held by the provided directory. 321 func KeyFromDir(dir Directory, name, title, class string) Key { 322 f := fileOf(dir) 323 var k Key 324 switch v := dir.(type) { 325 case *File: 326 k = newKey(&v.dir, name, title, class, 0, f) 327 case *tdirectoryFile: 328 k = newKey(v, name, title, class, 0, f) 329 default: 330 panic(fmt.Errorf("riofs: invalid directory type %T", dir)) 331 } 332 return k 333 } 334 335 func (k *Key) RVersion() int16 { return k.rvers } 336 337 func (*Key) Class() string { 338 return "TKey" 339 } 340 341 func (k *Key) ClassName() string { 342 return k.class 343 } 344 345 func (k *Key) Name() string { 346 return k.name 347 } 348 349 func (k *Key) Title() string { 350 return k.title 351 } 352 353 func (k *Key) Cycle() int { 354 return int(k.cycle) 355 } 356 357 func (k *Key) Nbytes() int32 { return k.nbytes } 358 func (k *Key) ObjLen() int32 { return k.objlen } 359 func (k *Key) KeyLen() int32 { return k.keylen } 360 func (k *Key) SeekKey() int64 { return k.seekkey } 361 func (k *Key) Buffer() []byte { return k.buf } 362 363 func (k *Key) SetFile(f *File) { k.f = f } 364 func (k *Key) SetBuffer(buf []byte) { k.buf = buf; k.objlen = int32(len(buf)) } 365 366 // ObjectType returns the Key's payload type. 367 // 368 // ObjectType returns nil if the Key's payload type is not known 369 // to the registry of groot. 370 func (k *Key) ObjectType() reflect.Type { 371 if k.otyp != nil { 372 return k.otyp 373 } 374 if !rtypes.Factory.HasKey(k.class) { 375 return nil 376 } 377 k.otyp = rtypes.Factory.Get(k.class)().Type() 378 return k.otyp 379 } 380 381 // Value returns the data corresponding to the Key's value 382 func (k *Key) Value() any { 383 v, err := k.Object() 384 if err != nil { 385 panic(fmt.Errorf("error loading payload for %q: %w", k.Name(), err)) 386 } 387 return v 388 } 389 390 // Object returns the (ROOT) object corresponding to the Key's value. 391 func (k *Key) Object() (root.Object, error) { 392 if k.obj != nil { 393 return k.obj, nil 394 } 395 396 buf, err := k.Bytes() 397 if err != nil { 398 return nil, fmt.Errorf("riofs: could not load key payload: %w", err) 399 } 400 401 fct := rtypes.Factory.Get(k.class) 402 if fct == nil { 403 return nil, fmt.Errorf("riofs: no registered factory for class %q (key=%q)", k.class, k.Name()) 404 } 405 406 v := fct() 407 obj, ok := v.Interface().(root.Object) 408 if !ok { 409 return nil, fmt.Errorf("riofs: class %q does not implement root.Object (key=%q)", k.class, k.Name()) 410 } 411 412 vv, ok := obj.(rbytes.Unmarshaler) 413 if !ok { 414 return nil, fmt.Errorf("riofs: class %q does not implement rbytes.Unmarshaler (key=%q)", k.class, k.Name()) 415 } 416 417 // use autogen context to automatically generate missing streamer infos when reading "old" files. 418 sictx := autogenCtx{k.f} 419 err = vv.UnmarshalROOT(rbytes.NewRBuffer(buf, nil, uint32(k.keylen), sictx)) 420 if err != nil { 421 return nil, fmt.Errorf("riofs: could not unmarshal key payload: %w", err) 422 } 423 424 if vv, ok := obj.(SetFiler); ok { 425 vv.SetFile(k.f) 426 } 427 if dir, ok := obj.(*tdirectoryFile); ok { 428 dir.file = k.f 429 dir.dir.parent = k.parent 430 dir.dir.named.SetName(k.Name()) 431 dir.dir.named.SetTitle(k.Name()) 432 dir.classname = k.class 433 err = dir.readKeys() 434 if err != nil { 435 return nil, err 436 } 437 } 438 439 k.obj = obj 440 return obj, nil 441 } 442 443 // Bytes returns the buffer of bytes corresponding to the Key's value 444 func (k *Key) Bytes() ([]byte, error) { 445 data, err := k.load(nil) 446 if err != nil { 447 return nil, err 448 } 449 return data, nil 450 } 451 452 func (k *Key) Load(buf []byte) ([]byte, error) { 453 return k.load(buf) 454 } 455 456 func (k *Key) load(buf []byte) ([]byte, error) { 457 buf = rbytes.ResizeU8(buf, int(k.objlen)) 458 if len(k.buf) > 0 { 459 copy(buf, k.buf) 460 return buf, nil 461 } 462 if k.isCompressed() { 463 start := k.seekkey + int64(k.keylen) 464 sr := io.NewSectionReader(k.f, start, int64(k.nbytes)-int64(k.keylen)) 465 err := rcompress.Decompress(buf, sr) 466 if err != nil { 467 return nil, fmt.Errorf("riofs: could not decompress key payload: %w", err) 468 } 469 return buf, nil 470 } 471 start := k.seekkey + int64(k.keylen) 472 r := io.NewSectionReader(k.f, start, int64(k.nbytes)) 473 _, err := io.ReadFull(r, buf) 474 if err != nil { 475 return nil, fmt.Errorf("riofs: could not read key payload: %w", err) 476 } 477 return buf, nil 478 } 479 480 // func (k *Key) store() error { 481 // if k.buf != nil { 482 // return nil 483 // } 484 // 485 // k.keylen = k.sizeof() 486 // 487 // buf := rbytes.NewWBuffer(make([]byte, k.objlen), nil, uint32(k.keylen), k.f) 488 // _, err := k.obj.(rbytes.Marshaler).MarshalROOT(buf) 489 // if err != nil { 490 // return err 491 // } 492 // k.objlen = int32(len(buf.Bytes())) 493 // k.buf, err = rcompress.Compress(nil, buf.Bytes(), k.f.compression) 494 // if err != nil { 495 // return err 496 // } 497 // nbytes := int32(len(k.buf)) 498 // k.nbytes = k.keylen + nbytes 499 // 500 // if k.seekkey <= 0 { 501 // panic("impossible: seekkey <= 0") 502 // } 503 // 504 // return nil 505 // } 506 507 func (k *Key) isCompressed() bool { 508 return k.objlen != k.nbytes-k.keylen 509 } 510 511 func (k *Key) isBigFile() bool { 512 return k.rvers > 1000 513 } 514 515 // sizeof returns the size in bytes of the key header structure. 516 func (k *Key) sizeof() int32 { 517 return keylenFor(k.name, k.title, k.class, &k.f.dir, k.f.end) 518 } 519 520 func keylenFor(name, title, class string, dir *tdirectoryFile, eof int64) int32 { 521 nbytes := int32(22) 522 if dir.isBigFile() || eof > kStartBigFile { 523 nbytes += 8 524 } 525 nbytes += datimeSizeof() 526 nbytes += tstringSizeof(class) 527 nbytes += tstringSizeof(name) 528 nbytes += tstringSizeof(title) 529 if class == "TBasket" { 530 nbytes += 2 // version 531 nbytes += 4 // bufsize 532 nbytes += 4 // nevsize 533 nbytes += 4 // nevbuf 534 nbytes += 4 // last 535 nbytes += 1 // flag 536 } 537 return nbytes 538 } 539 540 // MarshalROOT encodes the key to the provided buffer. 541 func (k *Key) MarshalROOT(w *rbytes.WBuffer) (int, error) { 542 if w.Err() != nil { 543 return 0, w.Err() 544 } 545 546 pos := w.Pos() 547 548 w.WriteI32(k.nbytes) 549 if k.nbytes < 0 { 550 return int(w.Pos() - pos), nil 551 } 552 553 if k.seekkey > kStartBigFile { 554 if k.rvers < 1000 { 555 k.rvers += 1000 556 } 557 } 558 559 w.WriteI16(k.RVersion()) 560 w.WriteI32(k.objlen) 561 w.WriteU32(time2datime(k.datetime)) 562 w.WriteI16(int16(k.keylen)) 563 w.WriteI16(k.cycle) 564 switch { 565 case k.isBigFile(): 566 w.WriteI64(k.seekkey) 567 // FIXME(sbinet): handle PidOffsetShift and PidOffset that are stored in the 16 highest bits of seekpdir 568 w.WriteI64(k.seekpdir) 569 default: 570 w.WriteI32(int32(k.seekkey)) 571 w.WriteI32(int32(k.seekpdir)) 572 } 573 w.WriteString(k.class) 574 w.WriteString(k.name) 575 w.WriteString(k.title) 576 577 return int(w.Pos() - pos), nil 578 } 579 580 // UnmarshalROOT decodes the content of data into the Key 581 func (k *Key) UnmarshalROOT(r *rbytes.RBuffer) error { 582 if r.Err() != nil { 583 return r.Err() 584 } 585 586 k.nbytes = r.ReadI32() 587 if k.nbytes < 0 { 588 k.class = "[GAP]" 589 return nil 590 } 591 592 k.rvers = r.ReadI16() 593 k.objlen = r.ReadI32() 594 k.datetime = datime2time(r.ReadU32()) 595 k.keylen = int32(r.ReadI16()) 596 k.cycle = r.ReadI16() 597 598 switch { 599 case k.isBigFile(): 600 k.seekkey = r.ReadI64() 601 // FIXME(sbinet): handle PidOffsetShift and PidOffset that are stored in the 16 highest bits of seekpdir 602 k.seekpdir = r.ReadI64() 603 default: 604 k.seekkey = int64(r.ReadI32()) 605 k.seekpdir = int64(r.ReadI32()) 606 } 607 608 k.class = r.ReadString() 609 k.name = r.ReadString() 610 k.title = r.ReadString() 611 612 return r.Err() 613 } 614 615 // writeFile writes the key's payload to the file 616 func (k *Key) writeFile(f *File) (int, error) { 617 if k.left > 0 { 618 w := rbytes.NewWBuffer(nil, nil, 0, nil) 619 w.WriteI32(int32(-k.left)) 620 k.buf = append(k.buf, w.Bytes()...) 621 } 622 623 buf := rbytes.NewWBuffer(make([]byte, k.nbytes), nil, 0, f) 624 _, err := k.MarshalROOT(buf) 625 if err != nil { 626 return 0, err 627 } 628 629 n, err := f.w.WriteAt(buf.Bytes(), k.seekkey) 630 if err != nil { 631 return n, err 632 } 633 nn, err := f.w.WriteAt(k.buf, k.seekkey+int64(k.keylen)) 634 n += nn 635 if err != nil { 636 return n, err 637 } 638 639 k.buf = nil 640 return n, nil 641 } 642 643 func (k *Key) records(w io.Writer, indent int) error { 644 hdr := strings.Repeat(" ", indent) 645 fmt.Fprintf(w, "%s=== key %q ===\n", hdr, k.Name()) 646 fmt.Fprintf(w, "%snbytes: %d\n", hdr, k.nbytes) 647 fmt.Fprintf(w, "%skeylen: %d\n", hdr, k.keylen) 648 fmt.Fprintf(w, "%sobjlen: %d\n", hdr, k.objlen) 649 fmt.Fprintf(w, "%scycle: %d\n", hdr, k.cycle) 650 fmt.Fprintf(w, "%sseek-key: %d\n", hdr, k.seekkey) 651 fmt.Fprintf(w, "%sseek-pdir: %d\n", hdr, k.seekpdir) 652 fmt.Fprintf(w, "%sclass: %q\n", hdr, k.class) 653 parent := "<nil>" 654 if k.parent != nil { 655 parent = fmt.Sprintf("@%d", k.parent.(*tdirectoryFile).seekdir) 656 } 657 fmt.Fprintf(w, "%sparent: %s\n", hdr, parent) 658 659 switch k.class { 660 case "TDirectory", "TDirectoryFile": 661 obj, err := k.Object() 662 if err != nil { 663 return fmt.Errorf("could not load object of key %q: %w", k.Name(), err) 664 } 665 return obj.(*tdirectoryFile).records(w, indent+1) 666 } 667 return nil 668 } 669 670 func init() { 671 f := func() reflect.Value { 672 o := &Key{} 673 return reflect.ValueOf(o) 674 } 675 rtypes.Factory.Add("TKey", f) 676 } 677 678 var ( 679 _ root.Object = (*Key)(nil) 680 _ root.Named = (*Key)(nil) 681 _ rbytes.Marshaler = (*Key)(nil) 682 _ rbytes.Unmarshaler = (*Key)(nil) 683 )