From e13cfce6b10da044833e5766058868c4798588b6 Mon Sep 17 00:00:00 2001 From: Prabhjot Singh Sethi Date: Tue, 27 May 2025 09:24:30 +0000 Subject: [PATCH] Add Table FindMany capability and streamline basic constructs ensure always avoiding use of pointers for table initialization where K and E will never be allowed to be a pointer Also add relevant section with exampleson how to use this library Signed-off-by: Prabhjot Singh Sethi --- table/generic.go | 146 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 22 deletions(-) diff --git a/table/generic.go b/table/generic.go index c9dd7ab..ba6181f 100644 --- a/table/generic.go +++ b/table/generic.go @@ -13,35 +13,109 @@ import ( "github.com/Prabhjot-Sethi/core/reconciler" ) -// Generic table type providing common functions types to specific -// structures each table built using. -// ensure performing sanity checks and ensuring common -// functionality +/* +Package table provides a generic Table abstraction for managing collections of entries +in a database, with built-in support for reconciliation, key management, and CRUD operations. + +# Overview + +The Table type is a generic structure that simplifies the implementation of strongly-typed +tables backed by a database. It provides: + +- Type safety for keys and entries. +- Automatic key type registration with the underlying db.StoreCollection. +- CRUD operations (Insert, Update, Find, Delete, etc.). +- Integration with a reconciler for event-driven updates. +- Sanity checks to prevent common mistakes (e.g., pointer types for entries or keys). + +# Usage + +To use this library, define your key and entry types, then create a Table instance: + + type MyKey struct { + ID string + } + type MyEntry struct { + Name string + Value int + } + + var myTable table.Table[MyKey, MyEntry] + + // Initialize with a db.StoreCollection (e.g., from your database layer) + err := myTable.Initialize(myCollection) + if err != nil { + // handle error + } + + // Insert an entry + entry := MyEntry{Name: "foo", Value: 42} + key := MyKey{ID: "abc"} + err = myTable.Insert(ctx, &key, &entry) + + // Find an entry + found, err := myTable.Find(ctx, &key) + + // Update an entry + entry.Value = 100 + err = myTable.Update(ctx, &key, &entry) + + // Delete an entry + err = myTable.DeleteKey(ctx, &key) + +# Notes + +- The entry type E must NOT be a pointer type. +- The key type K must NOT be a pointer type. +- The Table must be initialized before use. +- All operations are context-aware for cancellation and timeouts. + +*/ + +// Table is a generic table type providing common functions and types to specific +// structures each table is built using. It ensures sanity checks and provides +// common functionality for database-backed tables. +// +// K: Key type (must NOT be a pointer type, typically a struct or primitive) +// E: Entry type (must NOT be a pointer type) type Table[K any, E any] struct { reconciler.ManagerImpl col db.StoreCollection } +// Initialize sets up the Table with the provided db.StoreCollection. +// It performs sanity checks on the entry and key types and registers the key type with the collection. +// Must be called before any other operation. +// +// Returns an error if the table is already initialized, the entry or key type is a pointer, +// or if the collection setup fails. func (t *Table[K, E]) Initialize(col db.StoreCollection) error { if t.col != nil { return errors.Wrapf(errors.AlreadyExists, "Table is already initialized") } - var key [0]K - kType := reflect.TypeOf(key).Elem() - if kType.Kind() != reflect.Pointer { - kType = reflect.PointerTo(kType) + + var e E + if reflect.TypeOf(e).Kind() == reflect.Pointer { + return errors.Wrapf(errors.InvalidArgument, "Table entry type must not be a pointer") + } + + var k K + if reflect.TypeOf(k).Kind() == reflect.Pointer { + return errors.Wrapf(errors.InvalidArgument, "Table key type must not be a pointer") } - err := col.SetKeyType(kType) + err := col.SetKeyType(reflect.PointerTo(reflect.TypeOf(k))) if err != nil { return err } + // Register callback for collection changes err = col.Watch(context.Background(), nil, t.callback) if err != nil { return err } + // Initialize the reconciler manager err = t.ManagerImpl.Initialize(context.Background(), t) if err != nil { return err @@ -51,14 +125,18 @@ func (t *Table[K, E]) Initialize(col db.StoreCollection) error { return nil } +// callback is invoked on collection changes and notifies the reconciler. func (t *Table[K, E]) callback(op string, wKey any) { t.NotifyCallback(wKey) } +// keyOnly is a helper struct for extracting keys from the collection. type keyOnly[K any] struct { Key K `bson:"_id,omitempty"` } +// ReconcilerGetAllKeys returns all keys in the table. +// Used by the reconciler to enumerate all managed entries. func (t *Table[K, E]) ReconcilerGetAllKeys() []any { list := []keyOnly[K]{} keys := []any{} @@ -72,41 +150,65 @@ func (t *Table[K, E]) ReconcilerGetAllKeys() []any { return []any(keys) } -// Inserts a new entry to the table -func (t *Table[K, E]) Insert(ctx context.Context, key K, entry E) error { +// Insert adds a new entry to the table with the given key. +// Returns an error if the table is not initialized or the insert fails. +func (t *Table[K, E]) Insert(ctx context.Context, key *K, entry *E) error { if t.col == nil { return errors.Wrapf(errors.InvalidArgument, "Table not initialized") } return t.col.InsertOne(ctx, key, entry) } -// Locates an entry in the table, inserts if it doesn't exists -// and updates the data if it already exists -func (t *Table[K, E]) Locate(ctx context.Context, key K, entry E) error { +// Locate finds an entry by key, inserts it if it doesn't exist, or updates it if it does. +// Returns an error if the table is not initialized or the operation fails. +func (t *Table[K, E]) Locate(ctx context.Context, key *K, entry *E) error { if t.col == nil { return errors.Wrapf(errors.InvalidArgument, "Table not initialized") } return t.col.UpdateOne(ctx, key, entry, true) } -// Updates an existing entry -func (t *Table[K, E]) Update(ctx context.Context, key K, entry E) error { +// Update modifies an existing entry with the given key. +// Returns an error if the table is not initialized or the update fails. +func (t *Table[K, E]) Update(ctx context.Context, key *K, entry *E) error { if t.col == nil { return errors.Wrapf(errors.InvalidArgument, "Table not initialized") } return t.col.UpdateOne(ctx, key, entry, false) } -// Find an existing entry from the table -func (t *Table[K, E]) Find(ctx context.Context, key K, data E) error { +// Find retrieves an entry by key. +// Returns the entry and error if not found or if the table is not initialized. +func (t *Table[K, E]) Find(ctx context.Context, key *K) (*E, error) { + var data E if t.col == nil { - return errors.Wrapf(errors.InvalidArgument, "Table not initialized") + return nil, errors.Wrapf(errors.InvalidArgument, "Table not initialized") + } + err := t.col.FindOne(ctx, key, &data) + if err != nil { + return nil, errors.Wrapf(errors.NotFound, "failed to find entry with key %v: %s", key, err) } - return t.col.FindOne(ctx, key, data) + return &data, err +} + +// FindMany retrieves multiple entries matching the provided filter. +// Returns a slice of entries and error if none found or if the table is not initialized. +func (t *Table[K, E]) FindMany(ctx context.Context, filter any) ([]*E, error) { + if t.col == nil { + return nil, errors.Wrapf(errors.InvalidArgument, "Table not initialized") + } + var data []*E + err := t.col.FindMany(ctx, filter, &data) + if err != nil { + return nil, errors.Wrapf(errors.NotFound, "failed to find any entry: %s", err) + } + + return data, nil } -// Delete a specific key from the table -func (t *Table[K, E]) DeleteKey(ctx context.Context, key K) error { +// DeleteKey removes an entry by key from the table. +// Returns an error if the table is not initialized or the delete fails. +func (t *Table[K, E]) DeleteKey(ctx context.Context, key *K) error { if t.col == nil { return errors.Wrapf(errors.InvalidArgument, "Table not initialized") }