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  }