go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/db/dbloader/loader.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package dbloader 9 10 import ( 11 "context" 12 13 "go.charczuk.com/sdk/db" 14 ) 15 16 // LoaderFor returns a loader for a given type. 17 // 18 // Specifically it uses a `GetMany` command on the dbc to fetch 19 // the keys for a given batch. 20 // 21 // There are very specific constraints on the types we can use as the values here, specifically 22 // the types can only have (1) primary key, and the key values will be resolved 23 // with reflection incurring a bit of a performance hit. 24 func LoaderFor[K comparable, V any](dbc *db.Connection, opts ...db.InvocationOption) Loader[K, V] { 25 var v V 26 primaryKeys := db.TypeMetaFor(v).PrimaryKeys() 27 if len(primaryKeys) == 0 { 28 panic("LoaderFor provided type does not have any primary keys") 29 } 30 if len(primaryKeys) > 1 { 31 panic("LoaderFor provided type has multiple primary keys") 32 } 33 primaryKey := primaryKeys[0] 34 keyAccessor := func(v V) (k K) { 35 rawKeyValue := primaryKey.GetValue(v) 36 typedKeyValue, ok := rawKeyValue.(K) 37 if !ok { 38 return 39 } 40 return typedKeyValue 41 } 42 return Loader[K, V]{ 43 Resolver: func(ctx context.Context, keys ...K) (output map[K]V, err error) { 44 // ~~ UGLY HACK ALERT ~~ 45 // we have to do this because []K is not assignable to []any 46 // for reasons that i'm sure the go team has a patronizing 47 // answer for but regardless the ergonomics are awful. 48 anyKeys := make([]any, len(keys)) 49 for index := range keys { 50 anyKeys[index] = keys[index] 51 } 52 var values []V 53 err = dbc.Invoke(append(opts, db.OptContext(ctx))...).GetMany(&values, anyKeys...) 54 if err != nil { 55 return 56 } 57 output = make(map[K]V) 58 for _, v := range values { 59 k := keyAccessor(v) 60 output[k] = v 61 } 62 return 63 }, 64 } 65 } 66 67 // Loader is a specific "batch" of calls to 68 // the loader resolver. 69 type Loader[K comparable, V any] struct { 70 Resolver func(context.Context, ...K) (map[K]V, error) 71 } 72 73 func (l Loader[K, V]) New() *Batch[K, V] { 74 return &Batch[K, V]{ 75 loader: &l, 76 storage: make(map[K]*V), 77 } 78 } 79 80 type Batch[K comparable, V any] struct { 81 loader *Loader[K, V] 82 storage map[K]*V 83 } 84 85 // Load returns a promise to load a value with a given key. 86 func (b *Batch[K, V]) Load(k K) *V { 87 if v, ok := b.storage[k]; ok { 88 return v 89 } 90 // store ~something~ until we resolve 91 // we can't store <nil> here because we 92 // have to deref this pointer when we resolve 93 var v V 94 b.storage[k] = &v 95 return &v 96 } 97 98 // Keys returns the "scheduled" keys to be fetched. 99 func (b *Batch[K, V]) Keys() (output []K) { 100 output = make([]K, 0, len(b.storage)) 101 for k := range b.storage { 102 output = append(output, k) 103 } 104 return 105 } 106 107 // Resolve resoles the "promises" for the values for given keys and 108 // makes the stored pointers point at valid targets. 109 func (b *Batch[K, V]) Resolve(ctx context.Context) error { 110 values, err := b.loader.Resolver(ctx, b.Keys()...) 111 if err != nil { 112 return err 113 } 114 for k, v := range values { 115 if sv, ok := b.storage[k]; ok { 116 // this assignment is _very_ important 117 // we have to set the _target_ of the pointer 118 // to our value, not set the _variable_ to the address 119 // of our value. 120 *sv = v 121 } 122 } 123 return nil 124 }