Compare commits
	
		
			8 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ceff0161c6 | |||
| a30da61419 | |||
| b613b122e3 | |||
| d017530444 | |||
| 8de83cc290 | |||
| 603ec82b83 | |||
| 93c4cf31a8 | |||
| dc2d8a9103 | 
| @@ -11,6 +11,7 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
|  | 	"path/filepath" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| @@ -24,6 +25,7 @@ type EnumDefVal struct { | |||||||
|  |  | ||||||
| type EnumDef struct { | type EnumDef struct { | ||||||
| 	File         string | 	File         string | ||||||
|  | 	FileRelative string | ||||||
| 	EnumTypeName string | 	EnumTypeName string | ||||||
| 	Type         string | 	Type         string | ||||||
| 	Values       []EnumDefVal | 	Values       []EnumDefVal | ||||||
| @@ -83,7 +85,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { | |||||||
|  |  | ||||||
| 	for _, f := range files { | 	for _, f := range files { | ||||||
| 		fmt.Printf("========= %s =========\n\n", f.Name()) | 		fmt.Printf("========= %s =========\n\n", f.Name()) | ||||||
| 		fileEnums, pn, err := processFile(path.Join(sourceDir, f.Name())) | 		fileEnums, pn, err := processFile(sourceDir, path.Join(sourceDir, f.Name())) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -123,7 +125,7 @@ func GenerateEnumSpecs(sourceDir string, destFile string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func processFile(fn string) ([]EnumDef, string, error) { | func processFile(basedir string, fn string) ([]EnumDef, string, error) { | ||||||
| 	file, err := os.Open(fn) | 	file, err := os.Open(fn) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, "", err | 		return nil, "", err | ||||||
| @@ -153,8 +155,15 @@ func processFile(fn string) ([]EnumDef, string, error) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if match, ok := rexEnumDef.MatchFirst(line); ok { | 		if match, ok := rexEnumDef.MatchFirst(line); ok { | ||||||
|  |  | ||||||
|  | 			rfp, err := filepath.Rel(basedir, fn) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, "", err | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			def := EnumDef{ | 			def := EnumDef{ | ||||||
| 				File:         fn, | 				File:         fn, | ||||||
|  | 				FileRelative: rfp, | ||||||
| 				EnumTypeName: match.GroupByName("name").Value(), | 				EnumTypeName: match.GroupByName("name").Value(), | ||||||
| 				Type:         match.GroupByName("type").Value(), | 				Type:         match.GroupByName("type").Value(), | ||||||
| 				Values:       make([]EnumDefVal, 0), | 				Values:       make([]EnumDefVal, 0), | ||||||
| @@ -239,7 +248,7 @@ func fmtOutput(cs string, enums []EnumDef, pkgname string) string { | |||||||
|  |  | ||||||
| 		str += "// ================================ " + enumdef.EnumTypeName + " ================================" + "\n" | 		str += "// ================================ " + enumdef.EnumTypeName + " ================================" + "\n" | ||||||
| 		str += "//" + "\n" | 		str += "//" + "\n" | ||||||
| 		str += "// File:       " + enumdef.File + "\n" | 		str += "// File:       " + enumdef.FileRelative + "\n" | ||||||
| 		str += "// StringEnum: " + langext.Conditional(hasStr, "true", "false") + "\n" | 		str += "// StringEnum: " + langext.Conditional(hasStr, "true", "false") + "\n" | ||||||
| 		str += "// DescrEnum:  " + langext.Conditional(hasDescr, "true", "false") + "\n" | 		str += "// DescrEnum:  " + langext.Conditional(hasDescr, "true", "false") + "\n" | ||||||
| 		str += "//" + "\n" | 		str += "//" + "\n" | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| package goext | package goext | ||||||
|  |  | ||||||
| const GoextVersion = "0.0.151" | const GoextVersion = "0.0.159" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2023-06-07T17:57:03+0200" | const GoextVersionTimestamp = "2023-06-10T18:35:56+0200" | ||||||
|   | |||||||
| @@ -2,15 +2,32 @@ package wmo | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson/bsontype" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | 	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type EntityID = any | type EntityID interface { | ||||||
|  | 	MarshalBSONValue() (bsontype.Type, []byte, error) | ||||||
|  | 	String() string | ||||||
|  | } | ||||||
|  |  | ||||||
| type fullTypeRef[TData any] struct { | type Decodable interface { | ||||||
|  | 	Decode(v any) error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Cursorable interface { | ||||||
|  | 	Decode(v any) error | ||||||
|  | 	Err() error | ||||||
|  | 	Close(ctx context.Context) error | ||||||
|  | 	All(ctx context.Context, results any) error | ||||||
|  | 	RemainingBatchLength() int | ||||||
|  | 	Next(ctx context.Context) bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type fullTypeRef struct { | ||||||
| 	IsPointer      bool | 	IsPointer      bool | ||||||
| 	Kind           reflect.Kind | 	Kind           reflect.Kind | ||||||
| 	RealType       reflect.Type | 	RealType       reflect.Type | ||||||
| @@ -21,8 +38,11 @@ type fullTypeRef[TData any] struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Coll[TData any] struct { | type Coll[TData any] struct { | ||||||
| 	coll        *mongo.Collection | 	coll                *mongo.Collection                                        // internal mongo collection, access via Collection() | ||||||
| 	dataTypeMap map[string]fullTypeRef[TData] | 	dataTypeMap         map[string]fullTypeRef                                   // list of TData fields (only if TData is not an interface) | ||||||
|  | 	implDataTypeMap     map[reflect.Type]map[string]fullTypeRef                  // dynamic list of fields of TData implementations (only if TData is an interface) | ||||||
|  | 	customDecoder       *func(ctx context.Context, dec Decodable) (TData, error) // custom decoding function (useful if TData is an interface) | ||||||
|  | 	isInterfaceDataType bool                                                     // true if TData is an interface (not a struct) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) Collection() *mongo.Collection { | func (c *Coll[TData]) Collection() *mongo.Collection { | ||||||
| @@ -33,6 +53,14 @@ func (c *Coll[TData]) Name() string { | |||||||
| 	return c.coll.Name() | 	return c.coll.Name() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) WithDecodeFunc(cdf func(ctx context.Context, dec Decodable) (TData, error), example TData) *Coll[TData] { | ||||||
|  |  | ||||||
|  | 	c.EnsureInitializedReflection(example) | ||||||
|  |  | ||||||
|  | 	c.customDecoder = langext.Ptr(cdf) | ||||||
|  | 	return c | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) Indexes() mongo.IndexView { | func (c *Coll[TData]) Indexes() mongo.IndexView { | ||||||
| 	return c.coll.Indexes() | 	return c.coll.Indexes() | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								wmo/decoding.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								wmo/decoding.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | package wmo | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) decodeSingle(ctx context.Context, dec Decodable) (TData, error) { | ||||||
|  | 	if c.customDecoder != nil { | ||||||
|  |  | ||||||
|  | 		return (*c.customDecoder)(ctx, dec) | ||||||
|  |  | ||||||
|  | 	} else { | ||||||
|  |  | ||||||
|  | 		var res TData | ||||||
|  |  | ||||||
|  | 		err := dec.Decode(&res) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return *new(TData), err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return res, nil | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) decodeAll(ctx context.Context, cursor Cursorable) ([]TData, error) { | ||||||
|  | 	if c.customDecoder != nil { | ||||||
|  |  | ||||||
|  | 		res := make([]TData, 0, cursor.RemainingBatchLength()) | ||||||
|  |  | ||||||
|  | 		for cursor.Next(ctx) { | ||||||
|  | 			entry, err := (*c.customDecoder)(ctx, cursor) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 			res = append(res, entry) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return res, nil | ||||||
|  |  | ||||||
|  | 	} else { | ||||||
|  |  | ||||||
|  | 		res := make([]TData, 0, cursor.RemainingBatchLength()) | ||||||
|  |  | ||||||
|  | 		err := cursor.All(ctx, &res) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return res, nil | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -12,11 +12,27 @@ func (c *Coll[TData]) Aggregate(ctx context.Context, pipeline mongo.Pipeline, op | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	res := make([]TData, 0, cursor.RemainingBatchLength()) | 	res, err := c.decodeAll(ctx, cursor) | ||||||
| 	err = cursor.All(ctx, &res) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return res, nil | 	return res, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) AggregateOneOpt(ctx context.Context, pipeline mongo.Pipeline, opts ...*options.AggregateOptions) (*TData, error) { | ||||||
|  | 	cursor, err := c.coll.Aggregate(ctx, pipeline, opts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if cursor.Next(ctx) { | ||||||
|  | 		v, err := c.decodeSingle(ctx, cursor) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		return &v, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import ( | |||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) DeleteOne(ctx context.Context, id EntityID) error { | func (c *Coll[TData]) DeleteOneByID(ctx context.Context, id EntityID) error { | ||||||
| 	_, err := c.coll.DeleteOne(ctx, bson.M{"_id": id}) | 	_, err := c.coll.DeleteOne(ctx, bson.M{"_id": id}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -15,6 +15,15 @@ func (c *Coll[TData]) DeleteOne(ctx context.Context, id EntityID) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) DeleteOne(ctx context.Context, filterQuery bson.M) error { | ||||||
|  | 	_, err := c.coll.DeleteOne(ctx, filterQuery) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) DeleteMany(ctx context.Context, filterQuery bson.M) (*mongo.DeleteResult, error) { | func (c *Coll[TData]) DeleteMany(ctx context.Context, filterQuery bson.M) (*mongo.DeleteResult, error) { | ||||||
| 	res, err := c.coll.DeleteMany(ctx, filterQuery) | 	res, err := c.coll.DeleteMany(ctx, filterQuery) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -8,20 +8,15 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOne(ctx context.Context, filter bson.M) (TData, error) { | func (c *Coll[TData]) FindOne(ctx context.Context, filter bson.M) (TData, error) { | ||||||
| 	var res TData | 	mongoRes := c.coll.FindOne(ctx, filter) | ||||||
|  |  | ||||||
| 	err := c.coll.FindOne(ctx, filter).Decode(&res) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err != nil { |  | ||||||
| 		return *new(TData), err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return res, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneOpt(ctx context.Context, filter bson.M) (*TData, error) { | func (c *Coll[TData]) FindOneOpt(ctx context.Context, filter bson.M) (*TData, error) { | ||||||
| 	var res TData | 	mongoRes := c.coll.FindOne(ctx, filter) | ||||||
|  |  | ||||||
| 	err := c.coll.FindOne(ctx, filter).Decode(&res) | 	res, err := c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err == mongo.ErrNoDocuments { | 	if err == mongo.ErrNoDocuments { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| @@ -33,20 +28,15 @@ func (c *Coll[TData]) FindOneOpt(ctx context.Context, filter bson.M) (*TData, er | |||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneByID(ctx context.Context, id EntityID) (TData, error) { | func (c *Coll[TData]) FindOneByID(ctx context.Context, id EntityID) (TData, error) { | ||||||
| 	var res TData | 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": id}) | ||||||
|  |  | ||||||
| 	err := c.coll.FindOne(ctx, bson.M{"_id": id}).Decode(&res) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err != nil { |  | ||||||
| 		return *new(TData), err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return res, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneOptByID(ctx context.Context, id EntityID) (*TData, error) { | func (c *Coll[TData]) FindOneOptByID(ctx context.Context, id EntityID) (*TData, error) { | ||||||
| 	var res TData | 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": id}) | ||||||
|  |  | ||||||
| 	err := c.coll.FindOne(ctx, bson.M{"_id": id}).Decode(&res) | 	res, err := c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err == mongo.ErrNoDocuments { | 	if err == mongo.ErrNoDocuments { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| @@ -63,8 +53,7 @@ func (c *Coll[TData]) Find(ctx context.Context, filter bson.M, opts ...*options. | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	res := make([]TData, 0, cursor.RemainingBatchLength()) | 	res, err := c.decodeAll(ctx, cursor) | ||||||
| 	err = cursor.All(ctx, &res) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -11,12 +11,7 @@ func (c *Coll[TData]) InsertOne(ctx context.Context, valueIn TData) (TData, erro | |||||||
| 		return *new(TData), err | 		return *new(TData), err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var res TData | 	mongoRes := c.coll.FindOne(ctx, bson.M{"_id": insRes.InsertedID}) | ||||||
|  |  | ||||||
| 	err = c.coll.FindOne(ctx, bson.M{"_id": insRes.InsertedID}).Decode(&res) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err != nil { |  | ||||||
| 		return *new(TData), err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return res, nil |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,10 +37,19 @@ func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, | |||||||
| 		return nil, ct.CursorToken{}, err | 		return nil, ct.CursorToken{}, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// fast branch | ||||||
|  | 	if pageSize == nil { | ||||||
|  | 		entries, err := c.decodeAll(ctx, cursor) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, ct.CursorToken{}, err | ||||||
|  | 		} | ||||||
|  | 		return entries, ct.End(), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	entities := make([]TData, 0, cursor.RemainingBatchLength()) | 	entities := make([]TData, 0, cursor.RemainingBatchLength()) | ||||||
| 	for (pageSize == nil || len(entities) != *pageSize) && cursor.Next(ctx) { | 	for (pageSize == nil || len(entities) != *pageSize) && cursor.Next(ctx) { | ||||||
| 		var entry TData | 		var entry TData | ||||||
| 		err = cursor.Decode(&entry) | 		entry, err = c.decodeSingle(ctx, cursor) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, ct.CursorToken{}, err | 			return nil, ct.CursorToken{}, err | ||||||
| 		} | 		} | ||||||
| @@ -53,6 +62,8 @@ func (c *Coll[TData]) List(ctx context.Context, filter ct.Filter, pageSize *int, | |||||||
|  |  | ||||||
| 	last := entities[len(entities)-1] | 	last := entities[len(entities)-1] | ||||||
|  |  | ||||||
|  | 	c.EnsureInitializedReflection(last) | ||||||
|  |  | ||||||
| 	nextToken, err := c.createToken(sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, last, pageSize) | 	nextToken, err := c.createToken(sortPrimary, sortDirPrimary, sortSecondary, sortDirSecondary, last, pageSize) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, ct.CursorToken{}, err | 		return nil, ct.CursorToken{}, err | ||||||
|   | |||||||
| @@ -8,14 +8,9 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneAndUpdate(ctx context.Context, filterQuery bson.M, updateQuery bson.M) (TData, error) { | func (c *Coll[TData]) FindOneAndUpdate(ctx context.Context, filterQuery bson.M, updateQuery bson.M) (TData, error) { | ||||||
| 	var res TData | 	mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, updateQuery, options.FindOneAndUpdate().SetReturnDocument(options.After)) | ||||||
|  |  | ||||||
| 	err := c.coll.FindOneAndUpdate(ctx, filterQuery, updateQuery, options.FindOneAndUpdate().SetReturnDocument(options.After)).Decode(&res) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err != nil { |  | ||||||
| 		return *new(TData), err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return res, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) UpdateOne(ctx context.Context, filterQuery bson.M, updateQuery bson.M) error { | func (c *Coll[TData]) UpdateOne(ctx context.Context, filterQuery bson.M, updateQuery bson.M) error { | ||||||
| @@ -45,8 +40,8 @@ func (c *Coll[TData]) UpdateMany(ctx context.Context, filterQuery bson.M, update | |||||||
| 	return res, nil | 	return res, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) ReplaceOne(ctx context.Context, id EntityID, value TData) error { | func (c *Coll[TData]) ReplaceOne(ctx context.Context, filterQuery bson.M, value TData) error { | ||||||
| 	_, err := c.coll.UpdateOne(ctx, bson.M{"_id": id}, value) | 	_, err := c.coll.UpdateOne(ctx, filterQuery, bson.M{"$set": value}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -54,13 +49,8 @@ func (c *Coll[TData]) ReplaceOne(ctx context.Context, id EntityID, value TData) | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, id EntityID, value TData) (TData, error) { | func (c *Coll[TData]) FindOneAndReplace(ctx context.Context, filterQuery bson.M, value TData) (TData, error) { | ||||||
| 	var res TData | 	mongoRes := c.coll.FindOneAndUpdate(ctx, filterQuery, bson.M{"$set": value}, options.FindOneAndUpdate().SetReturnDocument(options.After)) | ||||||
|  |  | ||||||
| 	err := c.coll.FindOneAndUpdate(ctx, bson.M{"_id": id}, value, options.FindOneAndUpdate().SetReturnDocument(options.After)).Decode(&res) | 	return c.decodeSingle(ctx, mongoRes) | ||||||
| 	if err != nil { |  | ||||||
| 		return *new(TData), err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return res, nil |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,19 +7,56 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func (c *Coll[TData]) init() { | func (c *Coll[TData]) EnsureInitializedReflection(v TData) { | ||||||
|  |  | ||||||
| 	c.dataTypeMap = make(map[string]fullTypeRef[TData]) | 	if !c.isInterfaceDataType { | ||||||
|  | 		return // only dynamically load dataTypeMap on interface TData | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	rval := reflect.ValueOf(v) | ||||||
|  |  | ||||||
|  | 	for rval.Type().Kind() == reflect.Pointer { | ||||||
|  | 		rval = rval.Elem() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if _, ok := c.implDataTypeMap[rval.Type()]; ok { | ||||||
|  | 		return // already loaded | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m := make(map[string]fullTypeRef) | ||||||
|  |  | ||||||
|  | 	c.initFields("", rval, m, make([]int, 0)) | ||||||
|  |  | ||||||
|  | 	c.implDataTypeMap[rval.Type()] = m | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) init() { | ||||||
|  |  | ||||||
| 	example := *new(TData) | 	example := *new(TData) | ||||||
|  |  | ||||||
| 	v := reflect.ValueOf(example) | 	datatype := reflect.TypeOf(&example).Elem() | ||||||
|  |  | ||||||
| 	c.initFields("", v, make([]int, 0)) | 	if datatype.Kind() == reflect.Interface { | ||||||
|  |  | ||||||
|  | 		c.isInterfaceDataType = true | ||||||
|  |  | ||||||
|  | 		c.dataTypeMap = make(map[string]fullTypeRef) | ||||||
|  | 		c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef) | ||||||
|  | 	} else { | ||||||
|  |  | ||||||
|  | 		c.isInterfaceDataType = false | ||||||
|  |  | ||||||
|  | 		c.dataTypeMap = make(map[string]fullTypeRef) | ||||||
|  | 		c.implDataTypeMap = make(map[reflect.Type]map[string]fullTypeRef) | ||||||
|  |  | ||||||
|  | 		v := reflect.ValueOf(example) | ||||||
|  | 		c.initFields("", v, c.dataTypeMap, make([]int, 0)) | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int) { | } | ||||||
|  |  | ||||||
|  | func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, m map[string]fullTypeRef, idxarr []int) { | ||||||
|  |  | ||||||
| 	rtyp := rval.Type() | 	rtyp := rval.Type() | ||||||
|  |  | ||||||
| @@ -50,7 +87,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int | |||||||
|  |  | ||||||
| 		if rvfield.Type().Kind() == reflect.Pointer { | 		if rvfield.Type().Kind() == reflect.Pointer { | ||||||
|  |  | ||||||
| 			c.dataTypeMap[fullKey] = fullTypeRef[TData]{ | 			m[fullKey] = fullTypeRef{ | ||||||
| 				IsPointer:      true, | 				IsPointer:      true, | ||||||
| 				RealType:       rvfield.Type(), | 				RealType:       rvfield.Type(), | ||||||
| 				Kind:           rvfield.Type().Elem().Kind(), | 				Kind:           rvfield.Type().Elem().Kind(), | ||||||
| @@ -62,7 +99,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int | |||||||
|  |  | ||||||
| 		} else { | 		} else { | ||||||
|  |  | ||||||
| 			c.dataTypeMap[fullKey] = fullTypeRef[TData]{ | 			m[fullKey] = fullTypeRef{ | ||||||
| 				IsPointer:      false, | 				IsPointer:      false, | ||||||
| 				RealType:       rvfield.Type(), | 				RealType:       rvfield.Type(), | ||||||
| 				Kind:           rvfield.Type().Kind(), | 				Kind:           rvfield.Type().Kind(), | ||||||
| @@ -75,7 +112,7 @@ func (c *Coll[TData]) initFields(prefix string, rval reflect.Value, idxarr []int | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if rvfield.Kind() == reflect.Struct { | 		if rvfield.Kind() == reflect.Struct { | ||||||
| 			c.initFields(fullKey+".", rvfield, newIdxArr) | 			c.initFields(fullKey+".", rvfield, m, newIdxArr) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| @@ -102,7 +139,7 @@ func (c *Coll[TData]) getFieldValueAsTokenString(entity TData, fieldName string) | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Coll[TData]) getFieldType(fieldName string) fullTypeRef[TData] { | func (c *Coll[TData]) getFieldType(fieldName string) fullTypeRef { | ||||||
| 	return c.dataTypeMap[fieldName] | 	return c.dataTypeMap[fieldName] | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,14 @@ | |||||||
| package wmo | package wmo | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||||
| 	"go.mongodb.org/mongo-driver/mongo" | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | 	"gogs.mikescher.com/BlackForestBytes/goext/langext" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/rfctime" | 	"gogs.mikescher.com/BlackForestBytes/goext/rfctime" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/timeext" | 	"gogs.mikescher.com/BlackForestBytes/goext/timeext" | ||||||
| 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | 	"gogs.mikescher.com/BlackForestBytes/goext/tst" | ||||||
|  | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| @@ -177,3 +180,31 @@ func TestReflectionGetFieldValueAsTokenString(t *testing.T) { | |||||||
| 	tst.AssertEqual(t, gfvats(d, "cdate"), t0.Format(time.RFC3339Nano)) | 	tst.AssertEqual(t, gfvats(d, "cdate"), t0.Format(time.RFC3339Nano)) | ||||||
| 	tst.AssertEqual(t, gfvats(d, "mdate"), t0.Format(time.RFC3339Nano)) | 	tst.AssertEqual(t, gfvats(d, "mdate"), t0.Format(time.RFC3339Nano)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestReflectionWithInterface(t *testing.T) { | ||||||
|  |  | ||||||
|  | 	type TestData struct { | ||||||
|  | 		ID    primitive.ObjectID `bson:"_id"` | ||||||
|  | 		CDate time.Time          `bson:"cdate"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	type TestInterface interface { | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	coll1 := W[TestInterface](&mongo.Collection{}) | ||||||
|  |  | ||||||
|  | 	tst.AssertTrue(t, coll1.coll != nil) | ||||||
|  | 	tst.AssertEqual(t, 0, len(coll1.implDataTypeMap)) | ||||||
|  |  | ||||||
|  | 	df := func(ctx context.Context, dec Decodable) (TestInterface, error) { | ||||||
|  | 		return TestData{}, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	coll2 := W[TestInterface](&mongo.Collection{}).WithDecodeFunc(df, TestData{}) | ||||||
|  |  | ||||||
|  | 	tst.AssertTrue(t, coll2.coll != nil) | ||||||
|  | 	tst.AssertEqual(t, 1, len(coll2.implDataTypeMap)) | ||||||
|  |  | ||||||
|  | 	tst.AssertEqual(t, "ID", coll2.implDataTypeMap[reflect.TypeOf(TestData{})]["_id"].Name) | ||||||
|  | 	tst.AssertEqual(t, "CDate", coll2.implDataTypeMap[reflect.TypeOf(TestData{})]["cdate"].Name) | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user