hello world! 🌸
This commit is contained in:
commit
f46aab2f5f
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.exe
|
||||||
|
*.sum
|
||||||
|
muck/
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
26
go.mod
Normal file
26
go.mod
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
module rockfic.com/orm
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fatih/structtag v1.2.0
|
||||||
|
go.mongodb.org/mongo-driver v1.16.1
|
||||||
|
golang.org/x/net v0.21.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-loremipsum/loremipsum v1.1.3
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/klauspost/compress v1.13.6 // indirect
|
||||||
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace rockfic.com/orm => C:/rockfic/orm
|
3
go.work
Normal file
3
go.work
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
use .
|
74
idcounter.go
Normal file
74
idcounter.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
const COUNTER_COL = "@counters"
|
||||||
|
|
||||||
|
type Counter struct {
|
||||||
|
Current any `bson:"current"`
|
||||||
|
Collection string `bson:"collection"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLastInColl(cname string, id interface{}) interface{} {
|
||||||
|
var opts *options.FindOneOptions = options.FindOne()
|
||||||
|
|
||||||
|
switch id.(type) {
|
||||||
|
case int, int64, int32, uint, uint32, uint64, primitive.ObjectID:
|
||||||
|
opts.SetSort(bson.M{"_id": -1})
|
||||||
|
case string:
|
||||||
|
opts.SetSort(bson.M{"createdAt": -1})
|
||||||
|
default:
|
||||||
|
panic("unsupported id type provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cnt Counter
|
||||||
|
if !reflect.ValueOf(id).IsZero() {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
if err := DB.Collection(COUNTER_COL).FindOne(context.TODO(), bson.M{
|
||||||
|
"collection": cname,
|
||||||
|
}, opts).Decode(&cnt); err != nil {
|
||||||
|
cnt = Counter{
|
||||||
|
Current: id,
|
||||||
|
}
|
||||||
|
cnt.Collection = cname
|
||||||
|
switch nini := cnt.Current.(type) {
|
||||||
|
case int:
|
||||||
|
if nini == 0 {
|
||||||
|
cnt.Current = nini + 1
|
||||||
|
}
|
||||||
|
case int32:
|
||||||
|
if nini == int32(0) {
|
||||||
|
cnt.Current = nini + 1
|
||||||
|
}
|
||||||
|
case int64:
|
||||||
|
if nini == int64(0) {
|
||||||
|
cnt.Current = nini + 1
|
||||||
|
}
|
||||||
|
case uint:
|
||||||
|
if nini == uint(0) {
|
||||||
|
cnt.Current = nini + 1
|
||||||
|
}
|
||||||
|
case uint32:
|
||||||
|
if nini == uint32(0) {
|
||||||
|
cnt.Current = nini + 1
|
||||||
|
}
|
||||||
|
case uint64:
|
||||||
|
if nini == uint64(0) {
|
||||||
|
cnt.Current = nini + 1
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
cnt.Current = NextStringID()
|
||||||
|
case primitive.ObjectID:
|
||||||
|
cnt.Current = primitive.NewObjectID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cnt.Current
|
||||||
|
}
|
148
indexes.go
Normal file
148
indexes.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var optionKeywords = [...]string{"unique", "sparse", "background", "dropdups"}
|
||||||
|
|
||||||
|
// prolly won't need to use indexes, but just in case...
|
||||||
|
type InternalIndex struct {
|
||||||
|
Fields []string
|
||||||
|
Options []string
|
||||||
|
lastFieldIdx int
|
||||||
|
sticky bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *InternalIndex) appendOption(o string) {
|
||||||
|
o = strings.ToLower(o)
|
||||||
|
o = strings.Trim(o, " ")
|
||||||
|
for i := range optionKeywords {
|
||||||
|
if optionKeywords[i] == o {
|
||||||
|
in.Options = append(in.Options, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *InternalIndex) appendField(f string) {
|
||||||
|
in.Fields = append(in.Fields, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *InternalIndex) appendDotField(f string) {
|
||||||
|
in.Fields[in.lastFieldIdx] = in.Fields[in.lastFieldIdx] + "." + f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *InternalIndex) updateLastField() {
|
||||||
|
if len(in.Fields) > 0 {
|
||||||
|
in.lastFieldIdx = len(in.Fields) - 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
in.lastFieldIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *InternalIndex) setSticky(s bool) {
|
||||||
|
in.sticky = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *InternalIndex) getSticky() bool {
|
||||||
|
return in.sticky
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanIndex(src string) []InternalIndex {
|
||||||
|
var s scanner.Scanner
|
||||||
|
var parsed []InternalIndex
|
||||||
|
|
||||||
|
src = func(ss string) string {
|
||||||
|
return strings.Map(func(r rune) rune {
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, ss)
|
||||||
|
}(src)
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
file := fset.AddFile("", fset.Base(), len(src))
|
||||||
|
s.Init(file, []byte(src), nil, scanner.ScanComments)
|
||||||
|
|
||||||
|
lb := false
|
||||||
|
|
||||||
|
p := &InternalIndex{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, tok, lit := s.Scan()
|
||||||
|
|
||||||
|
switch tok {
|
||||||
|
case token.LBRACE:
|
||||||
|
if lb {
|
||||||
|
goto panik
|
||||||
|
}
|
||||||
|
lb = true
|
||||||
|
case token.RBRACE:
|
||||||
|
if !lb || len(p.Fields) == 0 {
|
||||||
|
goto panik
|
||||||
|
}
|
||||||
|
lb = false
|
||||||
|
case token.IDENT:
|
||||||
|
if lb {
|
||||||
|
if p.getSticky() {
|
||||||
|
p.appendDotField(lit)
|
||||||
|
p.setSticky(false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.appendField(lit)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.appendOption(lit)
|
||||||
|
case token.PERIOD:
|
||||||
|
if p.getSticky() {
|
||||||
|
goto panik
|
||||||
|
}
|
||||||
|
p.setSticky(true)
|
||||||
|
p.updateLastField()
|
||||||
|
case token.COMMA:
|
||||||
|
case token.COLON:
|
||||||
|
if lb {
|
||||||
|
goto panik
|
||||||
|
}
|
||||||
|
case token.SEMICOLON:
|
||||||
|
if lb {
|
||||||
|
goto panik
|
||||||
|
}
|
||||||
|
parsed = append(parsed, *p)
|
||||||
|
p = &InternalIndex{}
|
||||||
|
case token.EOF:
|
||||||
|
if lb {
|
||||||
|
goto panik
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
default:
|
||||||
|
goto panik
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panik:
|
||||||
|
panic("parsing error in index expression!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildIndex(i InternalIndex) *mongo.IndexModel {
|
||||||
|
idx := &mongo.IndexModel{
|
||||||
|
Keys: i.Fields,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range i.Options {
|
||||||
|
switch o {
|
||||||
|
case "unique":
|
||||||
|
idx.Options.SetUnique(true)
|
||||||
|
case "background":
|
||||||
|
idx.Options.SetBackground(true)
|
||||||
|
case "sparse":
|
||||||
|
idx.Options.SetSparse(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idx
|
||||||
|
}
|
371
model.go
Normal file
371
model.go
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatih/structtag"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Model - "base" struct for all queryable models
|
||||||
|
type Model struct {
|
||||||
|
// Created time. updated/added automatically.
|
||||||
|
Created time.Time `bson:"createdAt" json:"createdAt"`
|
||||||
|
// Modified time. updated/added automatically.
|
||||||
|
Modified time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||||
|
typeName string `bson:"-" json:"-"`
|
||||||
|
self any `bson:"-" json:"-"`
|
||||||
|
exists bool `bson:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasID is a simple interface that you must implement.
|
||||||
|
// This allows for more flexibility if your ID isn't an
|
||||||
|
// ObjectID (e.g., int, uint, string...).
|
||||||
|
//
|
||||||
|
// and yes, those darn ugly ObjectIDs are supported :)
|
||||||
|
type HasID interface {
|
||||||
|
Id() any
|
||||||
|
SetId(id any)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IModel interface {
|
||||||
|
Find(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error)
|
||||||
|
FindAll(query interface{}, opts ...*options.FindOptions) (*Query, error)
|
||||||
|
FindByID(id interface{}) (*Query, error)
|
||||||
|
FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error)
|
||||||
|
FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*Query, error)
|
||||||
|
getColl() *mongo.Collection
|
||||||
|
getIdxs() []*mongo.IndexModel
|
||||||
|
getParsedIdxs() map[string][]InternalIndex
|
||||||
|
Save() error
|
||||||
|
serializeToStore() primitive.M
|
||||||
|
setTypeName(str string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) setTypeName(str string) {
|
||||||
|
m.typeName = str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) getColl() *mongo.Collection {
|
||||||
|
_, ri, ok := ModelRegistry.HasByName(m.typeName)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("the model '%s' has not been registered", m.typeName))
|
||||||
|
}
|
||||||
|
return DB.Collection(ri.Collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) getIdxs() []*mongo.IndexModel {
|
||||||
|
mi := []*mongo.IndexModel{}
|
||||||
|
if mpi := m.getParsedIdxs(); mpi != nil {
|
||||||
|
for _, v := range mpi {
|
||||||
|
for _, i := range v {
|
||||||
|
mi = append(mi, BuildIndex(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mi
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) getParsedIdxs() map[string][]InternalIndex {
|
||||||
|
_, ri, ok := ModelRegistry.HasByName(m.typeName)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("model '%s' not registered", m.typeName))
|
||||||
|
}
|
||||||
|
return ri.Indexes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) Find(query interface{}, opts ...*options.FindOptions) (*mongo.Cursor, error) {
|
||||||
|
coll := m.getColl()
|
||||||
|
cursor, err := coll.Find(context.TODO(), query, opts...)
|
||||||
|
return cursor, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) FindAll(query interface{}, opts ...*options.FindOptions) (*Query, error) {
|
||||||
|
qqn := ModelRegistry.new_(m.typeName)
|
||||||
|
qqv := reflect.New(reflect.SliceOf(reflect.TypeOf(qqn).Elem()))
|
||||||
|
qqv.Elem().Set(reflect.Zero(qqv.Elem().Type()))
|
||||||
|
qq := &Query{
|
||||||
|
Model: *m,
|
||||||
|
Collection: m.getColl(),
|
||||||
|
doc: qqv.Interface(),
|
||||||
|
Op: OP_FIND_ALL,
|
||||||
|
}
|
||||||
|
q, err := m.Find(query, opts...)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
rawRes := bson.A{}
|
||||||
|
err = q.All(context.TODO(), &rawRes)
|
||||||
|
if err == nil {
|
||||||
|
m.exists = true
|
||||||
|
}
|
||||||
|
qq.rawDoc = rawRes
|
||||||
|
err = q.All(context.TODO(), &qq.doc)
|
||||||
|
if err != nil {
|
||||||
|
qq.reOrganize()
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return qq, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) FindPaged(query interface{}, page int64, perPage int64, options ...*options.FindOptions) (*Query, error) {
|
||||||
|
skipAmt := perPage * (page - 1)
|
||||||
|
if skipAmt < 0 {
|
||||||
|
skipAmt = 0
|
||||||
|
}
|
||||||
|
if len(options) > 0 {
|
||||||
|
options[0].SetSkip(skipAmt).SetLimit(perPage)
|
||||||
|
}
|
||||||
|
q, err := m.FindAll(query, options...)
|
||||||
|
q.Op = OP_FIND_PAGED
|
||||||
|
return q, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) FindByID(id interface{}) (*Query, error) {
|
||||||
|
return m.FindOne(bson.D{{"_id", id}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
||||||
|
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
||||||
|
|
||||||
|
// ^ ^
|
||||||
|
// ^ ^
|
||||||
|
|
||||||
|
func (m *Model) FindOne(query interface{}, options ...*options.FindOneOptions) (*Query, error) {
|
||||||
|
coll := m.getColl()
|
||||||
|
rip := coll.FindOne(context.TODO(), query, options...)
|
||||||
|
raw := bson.M{}
|
||||||
|
err := rip.Decode(&raw)
|
||||||
|
panik(err)
|
||||||
|
m.exists = true
|
||||||
|
qq := &Query{
|
||||||
|
Collection: m.getColl(),
|
||||||
|
rawDoc: raw,
|
||||||
|
doc: ModelRegistry.new_(m.typeName),
|
||||||
|
Op: OP_FIND_ONE,
|
||||||
|
Model: *m,
|
||||||
|
}
|
||||||
|
qq.rawDoc = raw
|
||||||
|
err = rip.Decode(qq.doc)
|
||||||
|
if err != nil {
|
||||||
|
qq.reOrganize()
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
m.self = qq.doc
|
||||||
|
return qq, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) Save() error {
|
||||||
|
var err error
|
||||||
|
c := m.getColl()
|
||||||
|
now := time.Now()
|
||||||
|
selfo := reflect.ValueOf(m.self)
|
||||||
|
vp := selfo
|
||||||
|
if vp.Kind() != reflect.Ptr {
|
||||||
|
vp = reflect.New(selfo.Type())
|
||||||
|
vp.Elem().Set(selfo)
|
||||||
|
}
|
||||||
|
var asHasId = vp.Interface().(HasID)
|
||||||
|
(asHasId).Id()
|
||||||
|
isNew := reflect.ValueOf(asHasId.Id()).IsZero() || !m.exists
|
||||||
|
if isNew {
|
||||||
|
m.Created = now
|
||||||
|
}
|
||||||
|
m.Modified = now
|
||||||
|
idxs := m.getIdxs()
|
||||||
|
for _, i := range idxs {
|
||||||
|
_, err = c.Indexes().CreateOne(context.TODO(), *i)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isNew {
|
||||||
|
nid := getLastInColl(c.Name(), asHasId.Id())
|
||||||
|
switch pnid := nid.(type) {
|
||||||
|
case uint:
|
||||||
|
nid = pnid + 1
|
||||||
|
case uint32:
|
||||||
|
nid = pnid + 1
|
||||||
|
case uint64:
|
||||||
|
nid = pnid + 1
|
||||||
|
case int:
|
||||||
|
nid = pnid + 1
|
||||||
|
case int32:
|
||||||
|
nid = pnid + 1
|
||||||
|
case int64:
|
||||||
|
nid = pnid + 1
|
||||||
|
case string:
|
||||||
|
nid = NextStringID()
|
||||||
|
case primitive.ObjectID:
|
||||||
|
nid = primitive.NewObjectID()
|
||||||
|
default:
|
||||||
|
panic("unknown or unsupported id type")
|
||||||
|
}
|
||||||
|
if reflect.ValueOf(asHasId.Id()).IsZero() {
|
||||||
|
(asHasId).SetId(nid)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.self = asHasId
|
||||||
|
c.InsertOne(context.TODO(), m.serializeToStore())
|
||||||
|
m.exists = true
|
||||||
|
} else {
|
||||||
|
_, err = c.ReplaceOne(context.TODO(), bson.D{{Key: "_id", Value: m.self.(HasID).Id()}}, m.serializeToStore())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) serializeToStore() bson.M {
|
||||||
|
return serializeIDs((*m).self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serializeIDs(input interface{}) bson.M {
|
||||||
|
ret := bson.M{}
|
||||||
|
mv := reflect.ValueOf(input)
|
||||||
|
mt := reflect.TypeOf(input)
|
||||||
|
|
||||||
|
vp := mv
|
||||||
|
if vp.Kind() != reflect.Ptr {
|
||||||
|
vp = reflect.New(mv.Type())
|
||||||
|
vp.Elem().Set(mv)
|
||||||
|
}
|
||||||
|
if mv.Kind() == reflect.Pointer {
|
||||||
|
mv = mv.Elem()
|
||||||
|
}
|
||||||
|
if mt.Kind() == reflect.Pointer {
|
||||||
|
mt = mt.Elem()
|
||||||
|
}
|
||||||
|
for i := 0; i < mv.NumField(); i++ {
|
||||||
|
fv := mv.Field(i)
|
||||||
|
ft := mt.Field(i)
|
||||||
|
var dr reflect.Value = fv
|
||||||
|
tag, err := structtag.Parse(string(mt.Field(i).Tag))
|
||||||
|
panik(err)
|
||||||
|
bbson, err := tag.Get("bson")
|
||||||
|
if err != nil || bbson.Name == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, terr := tag.Get("ref")
|
||||||
|
switch dr.Type().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
rarr := make([]interface{}, 0)
|
||||||
|
intArr := iFaceSlice(fv.Interface())
|
||||||
|
for _, idHaver := range intArr {
|
||||||
|
if terr == nil {
|
||||||
|
if reflect.ValueOf(idHaver).Type().Kind() != reflect.Pointer {
|
||||||
|
vp := reflect.New(reflect.ValueOf(idHaver).Type())
|
||||||
|
vp.Elem().Set(reflect.ValueOf(idHaver))
|
||||||
|
idHaver = vp.Interface()
|
||||||
|
}
|
||||||
|
ifc, ok := idHaver.(HasID)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("referenced model slice '%s' does not implement HasID", ft.Name))
|
||||||
|
}
|
||||||
|
rarr = append(rarr, ifc.Id())
|
||||||
|
} else if reflect.ValueOf(idHaver).Kind() == reflect.Struct {
|
||||||
|
rarr = append(rarr, serializeIDs(idHaver))
|
||||||
|
} else {
|
||||||
|
if reflect.ValueOf(idHaver).Kind() == reflect.Slice {
|
||||||
|
rarr = append(rarr, serializeIDSlice(iFaceSlice(idHaver)))
|
||||||
|
} else {
|
||||||
|
rarr = append(rarr, idHaver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret[bbson.Name] = rarr
|
||||||
|
}
|
||||||
|
case reflect.Pointer:
|
||||||
|
dr = fv.Elem()
|
||||||
|
fallthrough
|
||||||
|
case reflect.Struct:
|
||||||
|
if bbson.Name != "" {
|
||||||
|
if terr == nil {
|
||||||
|
idHaver := fv.Interface()
|
||||||
|
ifc, ok := idHaver.(HasID)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("referenced model '%s' does not implement HasID", ft.Name))
|
||||||
|
}
|
||||||
|
if !fv.IsNil() {
|
||||||
|
ret[bbson.Name] = ifc.Id()
|
||||||
|
} else {
|
||||||
|
ret[bbson.Name] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if reflect.TypeOf(fv.Interface()) != reflect.TypeOf(time.Now()) {
|
||||||
|
ret[bbson.Name] = serializeIDs(fv.Interface())
|
||||||
|
} else {
|
||||||
|
ret[bbson.Name] = fv.Interface()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for k, v := range serializeIDs(fv.Interface()) {
|
||||||
|
ret[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
ret[bbson.Name] = fv.Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func serializeIDSlice(input []interface{}) bson.A {
|
||||||
|
var a bson.A
|
||||||
|
for _, in := range input {
|
||||||
|
fv := reflect.ValueOf(in)
|
||||||
|
switch fv.Type().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
a = append(a, serializeIDSlice(iFaceSlice(fv.Interface())))
|
||||||
|
case reflect.Struct:
|
||||||
|
a = append(a, serializeIDs(fv.Interface()))
|
||||||
|
default:
|
||||||
|
a = append(a, fv.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new instance of a given model.
|
||||||
|
// returns a pointer to the newly created model.
|
||||||
|
func Create(d any) any {
|
||||||
|
var n string
|
||||||
|
var ri *InternalModel
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
n, ri, ok = ModelRegistry.HasByName(nameOf(d))
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
ModelRegistry.Model(d)
|
||||||
|
n, ri, _ = ModelRegistry.Has(d)
|
||||||
|
}
|
||||||
|
t := ri.Type
|
||||||
|
v := valueOf(d)
|
||||||
|
i := ModelRegistry.Index(n)
|
||||||
|
|
||||||
|
r := reflect.New(t)
|
||||||
|
|
||||||
|
r.Elem().Set(v)
|
||||||
|
|
||||||
|
df := r.Elem().Field(i)
|
||||||
|
dm := df.Interface().(Model)
|
||||||
|
if reflect.ValueOf(d).Kind() == reflect.Pointer {
|
||||||
|
r.Elem().Set(reflect.ValueOf(d).Elem())
|
||||||
|
} else {
|
||||||
|
r.Elem().Set(reflect.ValueOf(d))
|
||||||
|
}
|
||||||
|
dm.typeName = n
|
||||||
|
what := r.Interface()
|
||||||
|
dm.self = what
|
||||||
|
df.Set(reflect.ValueOf(dm))
|
||||||
|
return what
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Model) PrintMe() {
|
||||||
|
fmt.Printf("My name is %s !\n", nameOf(m))
|
||||||
|
}
|
109
model_test.go
Normal file
109
model_test.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
doc := Create(&iti_single).(*story)
|
||||||
|
assert.Equal(t, iti_single.Title, doc.Title)
|
||||||
|
assert.Equal(t, iti_single.Chapters[0].Summary, doc.Chapters[0].Summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSave(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
storyDoc := Create(iti_multi).(*story)
|
||||||
|
lauthor := Create(author).(*user)
|
||||||
|
storyDoc.Author = lauthor
|
||||||
|
assert.Equal(t, storyDoc.Id(), int64(0))
|
||||||
|
assert.Equal(t, lauthor.ID, storyDoc.Author.ID)
|
||||||
|
aerr := lauthor.Save()
|
||||||
|
assert.Equal(t, nil, aerr)
|
||||||
|
serr := storyDoc.Save()
|
||||||
|
assert.Equal(t, nil, serr)
|
||||||
|
assert.Less(t, int64(0), storyDoc.ID)
|
||||||
|
assert.Less(t, int64(0), lauthor.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPopulate(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
|
||||||
|
bandDoc := Create(iti_single.Chapters[0].Bands[0]).(*band)
|
||||||
|
storyDoc := Create(iti_single).(*story)
|
||||||
|
author := Create(author).(*user)
|
||||||
|
|
||||||
|
err := bandDoc.Save()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
storyDoc.Author = author
|
||||||
|
err = author.Save()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
err = storyDoc.Save()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Greater(t, storyDoc.ID, int64(0))
|
||||||
|
|
||||||
|
smodel := Create(story{}).(*story)
|
||||||
|
q, err := smodel.FindByID(storyDoc.ID)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
//assert.Greater(t, smodel.ID, int64(0))
|
||||||
|
assert.NotPanics(t, func() {
|
||||||
|
foundDoc := &story{}
|
||||||
|
q.Populate("Author", "Chapters.Bands").Exec(foundDoc)
|
||||||
|
j, _ := q.JSON()
|
||||||
|
fmt.Printf("%s\n", j)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
nb := Create(band{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Metallica",
|
||||||
|
Characters: []string{
|
||||||
|
"James Hetfield",
|
||||||
|
"Lars Ulrich",
|
||||||
|
"Kirk Hammett",
|
||||||
|
"Cliff Burton",
|
||||||
|
},
|
||||||
|
Locked: false,
|
||||||
|
}).(*band)
|
||||||
|
err := nb.Save()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
nb.Locked = true
|
||||||
|
err = nb.Save()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
foundM := Create(band{}).(*band)
|
||||||
|
q, err := foundM.FindByID(int64(1))
|
||||||
|
found := &band{}
|
||||||
|
q.Exec(found)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, int64(1), found.ID)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, true, found.Locked)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModel_FindAll(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
smodel := Create(story{}).(*story)
|
||||||
|
query, err := smodel.FindAll(bson.M{}, options.Find())
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
final := make([]story, 0)
|
||||||
|
query.Exec(&final)
|
||||||
|
assert.Greater(t, len(final), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModel_PopulateMulti(t *testing.T) {
|
||||||
|
initTest()
|
||||||
|
smodel := Create(story{}).(*story)
|
||||||
|
query, err := smodel.FindAll(bson.M{}, options.Find())
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
final := make([]story, 0)
|
||||||
|
query.Populate("Author", "Chapters.Bands").Exec(&final)
|
||||||
|
assert.Greater(t, len(final), 0)
|
||||||
|
}
|
482
query.go
Normal file
482
query.go
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fatih/structtag"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
// the handle of the collection associated with this query
|
||||||
|
Collection *mongo.Collection
|
||||||
|
// the operation from which this query stems
|
||||||
|
Op string
|
||||||
|
// the model instance associated with this query
|
||||||
|
Model Model
|
||||||
|
done bool
|
||||||
|
rawDoc any
|
||||||
|
doc any
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
OP_FIND_ONE = "findOne"
|
||||||
|
OP_FIND_PAGED = "findPaged"
|
||||||
|
OP_FIND_ALL = "findAll"
|
||||||
|
OP_FIND = "find"
|
||||||
|
)
|
||||||
|
|
||||||
|
func populate(r Reference, rcoll string, rawDoc interface{}, d string, src interface{}) any {
|
||||||
|
rt := reflect.TypeOf(src)
|
||||||
|
rv := reflect.ValueOf(src)
|
||||||
|
srt := rt
|
||||||
|
if srt.Kind() == reflect.Pointer {
|
||||||
|
srt = rt.Elem()
|
||||||
|
}
|
||||||
|
if rv.Kind() != reflect.Pointer {
|
||||||
|
rv = reflect.New(rt)
|
||||||
|
rv.Elem().Set(reflect.ValueOf(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldsMap [3]string
|
||||||
|
type bsonWhat struct {
|
||||||
|
What string
|
||||||
|
}
|
||||||
|
/*switch cl.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
|
||||||
|
}*/
|
||||||
|
var w bsonWhat
|
||||||
|
switch rawDoc.(type) {
|
||||||
|
case bson.A:
|
||||||
|
w.What = "A"
|
||||||
|
case bson.D:
|
||||||
|
w.What = "D"
|
||||||
|
case bson.M:
|
||||||
|
w.What = "M"
|
||||||
|
default:
|
||||||
|
w.What = "-"
|
||||||
|
}
|
||||||
|
var next string
|
||||||
|
if len(strings.Split(d, ".")) > 1 {
|
||||||
|
next = strings.Join(strings.Split(d, ".")[1:], ".")
|
||||||
|
d = strings.Split(d, ".")[0]
|
||||||
|
} else {
|
||||||
|
next = d
|
||||||
|
}
|
||||||
|
var toReturn interface{}
|
||||||
|
switch w.What {
|
||||||
|
case "A":
|
||||||
|
rvs := reflect.MakeSlice(rt, 0, 0)
|
||||||
|
var rahh reflect.Value
|
||||||
|
if rv.Kind() == reflect.Ptr {
|
||||||
|
rahh = rv.Elem()
|
||||||
|
} else {
|
||||||
|
rahh = rv
|
||||||
|
}
|
||||||
|
for i, el := range rawDoc.(bson.A) {
|
||||||
|
it := rahh.Index(i)
|
||||||
|
popped := populate(r, rcoll, el, next, it.Interface())
|
||||||
|
poppedVal := reflect.ValueOf(popped)
|
||||||
|
if poppedVal.Kind() == reflect.Pointer {
|
||||||
|
rvs = reflect.Append(rvs, poppedVal.Elem())
|
||||||
|
} else {
|
||||||
|
rvs = reflect.Append(rvs, poppedVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rv.CanSet() {
|
||||||
|
rv.Set(rvs)
|
||||||
|
} else if rv.Kind() == reflect.Pointer {
|
||||||
|
rv.Elem().Set(rvs)
|
||||||
|
} else {
|
||||||
|
src = rvs.Interface()
|
||||||
|
toReturn = src
|
||||||
|
}
|
||||||
|
case "D":
|
||||||
|
loc := rawDoc.(bson.D)
|
||||||
|
nrd := bson.M{}
|
||||||
|
for _, el := range loc {
|
||||||
|
nrd[el.Key] = el.Value
|
||||||
|
}
|
||||||
|
rawDoc = nrd
|
||||||
|
fallthrough
|
||||||
|
case "M":
|
||||||
|
dd := rawDoc.(bson.M)
|
||||||
|
var sf reflect.Value
|
||||||
|
var rsf reflect.Value
|
||||||
|
if rv.Kind() == reflect.Pointer {
|
||||||
|
sf = rv.Elem().FieldByName(d)
|
||||||
|
} else {
|
||||||
|
sf = rv.FieldByName(d)
|
||||||
|
}
|
||||||
|
if rv.Kind() == reflect.Pointer {
|
||||||
|
rsf = rv.Elem().FieldByName(d)
|
||||||
|
} else {
|
||||||
|
rsf = rv.FieldByName(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ff reflect.StructField
|
||||||
|
var ok bool
|
||||||
|
if rt.Kind() == reflect.Pointer {
|
||||||
|
ff, ok = rt.Elem().FieldByName(d)
|
||||||
|
} else {
|
||||||
|
ff, ok = rt.FieldByName(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
tag, err := structtag.Parse(string(ff.Tag))
|
||||||
|
if err == nil {
|
||||||
|
val, err2 := tag.Get("bson")
|
||||||
|
if err2 == nil {
|
||||||
|
fttt := ff.Type
|
||||||
|
if fttt.Kind() == reflect.Pointer || fttt.Kind() == reflect.Slice {
|
||||||
|
fttt = fttt.Elem()
|
||||||
|
}
|
||||||
|
fieldsMap = [3]string{d, fttt.Name(), val.Name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intermediate := populate(r, rcoll, dd[fieldsMap[2]], next, sf.Interface())
|
||||||
|
if rsf.CanSet() {
|
||||||
|
ival := reflect.ValueOf(intermediate)
|
||||||
|
if ival.Kind() != reflect.Pointer && rsf.Kind() == reflect.Pointer {
|
||||||
|
rsf.Set(ival.Elem())
|
||||||
|
} else if ival.Kind() == reflect.Pointer && rsf.Kind() != reflect.Pointer {
|
||||||
|
rsf.Set(ival.Elem())
|
||||||
|
} else {
|
||||||
|
rsf.Set(ival)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
src = intermediate
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
tto := r.HydratedType
|
||||||
|
if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice {
|
||||||
|
tto = tto.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
rawt := ModelRegistry.new_(tto.Name())
|
||||||
|
t := rawt.(IModel)
|
||||||
|
q := bson.M{"_id": rawDoc}
|
||||||
|
reso := DB.Collection(rcoll).FindOne(context.TODO(), q)
|
||||||
|
reso.Decode(t)
|
||||||
|
hatred := rv
|
||||||
|
if hatred.Kind() == reflect.Pointer {
|
||||||
|
hatred = hatred.Elem()
|
||||||
|
}
|
||||||
|
if hatred.CanSet() {
|
||||||
|
if reflect.ValueOf(t).Kind() == reflect.Pointer {
|
||||||
|
fmt.Println(reflect.ValueOf(t).Elem().Type().String())
|
||||||
|
fmt.Println(rv.Type().String())
|
||||||
|
hatred.Set(reflect.ValueOf(t).Elem())
|
||||||
|
} else {
|
||||||
|
hatred.Set(reflect.ValueOf(t))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
src = t
|
||||||
|
toReturn = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if toReturn == nil {
|
||||||
|
return rv.Interface()
|
||||||
|
}
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate populates document references via reflection
|
||||||
|
func (q *Query) Populate(fields ...string) *Query {
|
||||||
|
_, cm, _ := ModelRegistry.HasByName(q.Model.typeName)
|
||||||
|
|
||||||
|
if cm != nil {
|
||||||
|
rawDoc := q.rawDoc
|
||||||
|
for _, field := range fields {
|
||||||
|
// 0 = fieldname, 1 = typename, 2 = bson name
|
||||||
|
|
||||||
|
var r Reference
|
||||||
|
|
||||||
|
for k2, v := range cm.References {
|
||||||
|
if strings.HasPrefix(k2, field) {
|
||||||
|
r = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Exists {
|
||||||
|
// get self
|
||||||
|
// get ptr
|
||||||
|
// find
|
||||||
|
// unmarshal...
|
||||||
|
htt := r.HydratedType
|
||||||
|
if htt.Kind() == reflect.Pointer || htt.Kind() == reflect.Slice {
|
||||||
|
htt = htt.Elem()
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(field, ".") || strings.HasPrefix(field, ".") {
|
||||||
|
log.Printf("WARN: invalid field name passed to Populate(). skipping...\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tto := r.HydratedType
|
||||||
|
if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice {
|
||||||
|
tto = tto.Elem()
|
||||||
|
}
|
||||||
|
_, refColl, _ := ModelRegistry.HasByName(tto.Name())
|
||||||
|
var tmp1 interface{}
|
||||||
|
if arr, ok := rawDoc.(bson.A); ok {
|
||||||
|
typ := cm.Type
|
||||||
|
slic := reflect.New(reflect.SliceOf(typ))
|
||||||
|
for i, val2 := range arr {
|
||||||
|
ref := reflect.ValueOf(q.doc)
|
||||||
|
if ref.Kind() == reflect.Pointer {
|
||||||
|
ref = ref.Elem()
|
||||||
|
}
|
||||||
|
src := ref.Index(i).Interface()
|
||||||
|
inter := populate(r, refColl.Collection, val2, field, src)
|
||||||
|
if reflect.ValueOf(inter).Kind() == reflect.Pointer {
|
||||||
|
slic.Elem().Set(reflect.Append(slic.Elem(), reflect.ValueOf(inter).Elem()))
|
||||||
|
} else {
|
||||||
|
slic.Elem().Set(reflect.Append(slic, reflect.ValueOf(inter)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp1 = slic.Interface()
|
||||||
|
} else {
|
||||||
|
tmp1 = populate(r, refColl.Collection, rawDoc, field, reflect.ValueOf(q.doc).Interface())
|
||||||
|
}
|
||||||
|
q.doc = tmp1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
func (q *Query) reOrganize() {
|
||||||
|
var trvo reflect.Value
|
||||||
|
if arr, ok := q.rawDoc.(bson.A); ok {
|
||||||
|
typ := ModelRegistry[q.Model.typeName].Type
|
||||||
|
slic := reflect.New(reflect.SliceOf(typ))
|
||||||
|
for _, v2 := range arr {
|
||||||
|
inter := reflect.ValueOf(rerere(v2, typ))
|
||||||
|
if inter.Kind() == reflect.Pointer {
|
||||||
|
inter = inter.Elem()
|
||||||
|
}
|
||||||
|
slic.Elem().Set(reflect.Append(slic.Elem(), inter))
|
||||||
|
}
|
||||||
|
trvo = slic.Elem()
|
||||||
|
} else {
|
||||||
|
trvo = reflect.ValueOf(rerere(q.rawDoc, reflect.TypeOf(q.doc)))
|
||||||
|
for {
|
||||||
|
if trvo.Kind() == reflect.Pointer {
|
||||||
|
trvo = trvo.Elem()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resV := reflect.ValueOf(q.doc)
|
||||||
|
for {
|
||||||
|
if resV.Kind() == reflect.Pointer {
|
||||||
|
resV = resV.Elem()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resV.Set(trvo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func rerere(input interface{}, resType reflect.Type) interface{} {
|
||||||
|
t := reflect.TypeOf(input)
|
||||||
|
v := reflect.ValueOf(input)
|
||||||
|
if input == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if v.CanAddr() && v.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if v.Type().Kind() == reflect.Pointer {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() == reflect.Pointer {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if resType.Kind() == reflect.Pointer {
|
||||||
|
resType = resType.Elem()
|
||||||
|
}
|
||||||
|
resV := reflect.New(resType)
|
||||||
|
var rve reflect.Value = resV
|
||||||
|
if rve.Kind() == reflect.Pointer {
|
||||||
|
rve = resV.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if d, isD := v.Interface().(bson.D); isD {
|
||||||
|
m := bson.M{}
|
||||||
|
for _, el := range d {
|
||||||
|
m[el.Key] = el.Value
|
||||||
|
}
|
||||||
|
input = m
|
||||||
|
v = reflect.ValueOf(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resType.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
shouldBreak := false
|
||||||
|
mipmap, ok := v.Interface().(bson.M)
|
||||||
|
if ok {
|
||||||
|
for i := 0; i < resType.NumField(); i++ {
|
||||||
|
ft := resType.Field(i)
|
||||||
|
fv := rve.Field(i)
|
||||||
|
if ft.Anonymous {
|
||||||
|
fv.Set(handleAnon(input, ft.Type, fv))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tags, err := structtag.Parse(string(ft.Tag))
|
||||||
|
panik(err)
|
||||||
|
btag, err := tags.Get("bson")
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if btag.Name == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
intermediate := mipmap[btag.Name]
|
||||||
|
_, err = tags.Get("ref")
|
||||||
|
if err != nil {
|
||||||
|
tmp := rerere(intermediate, ft.Type)
|
||||||
|
fuck := reflect.ValueOf(tmp)
|
||||||
|
if !fuck.IsZero() {
|
||||||
|
if fuck.Type().Kind() == reflect.Pointer {
|
||||||
|
fuck = fuck.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fv.Set(fuck)
|
||||||
|
shouldBreak = true
|
||||||
|
} else {
|
||||||
|
tt := ft.Type
|
||||||
|
if tt.Kind() == reflect.Pointer {
|
||||||
|
tt = tt.Elem()
|
||||||
|
}
|
||||||
|
tmp := rerere(intermediate, ft.Type)
|
||||||
|
if tmp != nil {
|
||||||
|
if reflect.ValueOf(tmp).Kind() == reflect.Pointer && fv.Kind() != reflect.Pointer {
|
||||||
|
fv.Set(reflect.ValueOf(tmp).Elem())
|
||||||
|
} else {
|
||||||
|
fv.Set(reflect.ValueOf(tmp))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fv.Set(reflect.Zero(ft.Type))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shouldBreak {
|
||||||
|
// break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nunu := ModelRegistry.new_(resType.Name())
|
||||||
|
ider, ok := nunu.(HasID)
|
||||||
|
if ok {
|
||||||
|
ider.SetId(v.Interface())
|
||||||
|
if reflect.ValueOf(ider).Kind() == reflect.Pointer {
|
||||||
|
nunu = reflect.ValueOf(ider).Elem().Interface()
|
||||||
|
}
|
||||||
|
rve.Set(reflect.ValueOf(nunu))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
arr := v.Interface().(bson.A)
|
||||||
|
for _, it := range arr {
|
||||||
|
if it != nil {
|
||||||
|
tmp := reflect.ValueOf(rerere(it, rve.Type().Elem()))
|
||||||
|
if tmp.Kind() == reflect.Pointer {
|
||||||
|
tmp = tmp.Elem()
|
||||||
|
}
|
||||||
|
rve.Set(reflect.Append(rve, tmp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if resType.AssignableTo(v.Type()) {
|
||||||
|
rve.Set(reflect.ValueOf(input))
|
||||||
|
} else {
|
||||||
|
switch rve.Interface().(type) {
|
||||||
|
case int, int32, int64, uint, uint32, uint64:
|
||||||
|
rve.Set(reflect.ValueOf(coerceInt(v, rve)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resV.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAnon(raw interface{}, rtype reflect.Type, rval reflect.Value) reflect.Value {
|
||||||
|
f := rval
|
||||||
|
g := rtype
|
||||||
|
if rtype.Kind() == reflect.Pointer {
|
||||||
|
g = rtype.Elem()
|
||||||
|
}
|
||||||
|
if !f.CanSet() {
|
||||||
|
f = reflect.New(g)
|
||||||
|
f.Elem().Set(rval)
|
||||||
|
}
|
||||||
|
if rtype.Kind() != reflect.Struct {
|
||||||
|
return rval
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < rtype.NumField(); i++ {
|
||||||
|
typeField := rtype.Field(i)
|
||||||
|
valueField := f.Field(i)
|
||||||
|
tags, err := structtag.Parse(string(typeField.Tag))
|
||||||
|
if !typeField.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
var btag *structtag.Tag
|
||||||
|
btag, err = tags.Get("bson")
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if btag.Name == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
amap, ok := raw.(bson.M)
|
||||||
|
if ok {
|
||||||
|
fval := amap[btag.Name]
|
||||||
|
if reflect.TypeOf(fval) == reflect.TypeFor[primitive.DateTime]() {
|
||||||
|
fval = time.UnixMilli(int64(fval.(primitive.DateTime)))
|
||||||
|
}
|
||||||
|
if valueField.Kind() == reflect.Pointer {
|
||||||
|
valueField.Elem().Set(reflect.ValueOf(fval))
|
||||||
|
} else {
|
||||||
|
valueField.Set(reflect.ValueOf(fval))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON - marshals this Query's results into json format
|
||||||
|
func (q *Query) JSON() (string, error) {
|
||||||
|
res, err := json.MarshalIndent(q.doc, "\n", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(res[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec - executes the query and puts its results into the
|
||||||
|
// provided argument.
|
||||||
|
//
|
||||||
|
// Will panic if called more than once on the same Query instance.
|
||||||
|
func (q *Query) Exec(result interface{}) {
|
||||||
|
if q.done {
|
||||||
|
panic("Exec() has already been called!")
|
||||||
|
}
|
||||||
|
reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
|
||||||
|
q.done = true
|
||||||
|
}
|
351
registry.go
Normal file
351
registry.go
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
context2 "context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatih/structtag"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InternalModel, as the name suggests, is used
|
||||||
|
// internally by the model registry
|
||||||
|
type InternalModel struct {
|
||||||
|
Idx int
|
||||||
|
Type reflect.Type
|
||||||
|
Collection string
|
||||||
|
References map[string]Reference
|
||||||
|
Indexes map[string][]InternalIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference stores a typed document reference
|
||||||
|
type Reference struct {
|
||||||
|
// owning model name
|
||||||
|
Model string
|
||||||
|
// the name of the struct field
|
||||||
|
FieldName string
|
||||||
|
// index of field in owning struct
|
||||||
|
Idx int
|
||||||
|
// the type of the referenced object
|
||||||
|
HydratedType reflect.Type
|
||||||
|
|
||||||
|
// field kind (struct, slice, ...)
|
||||||
|
Kind reflect.Kind
|
||||||
|
|
||||||
|
Exists bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type TModelRegistry map[string]*InternalModel
|
||||||
|
|
||||||
|
// ModelRegistry - the ModelRegistry stores a map containing
|
||||||
|
// pointers to InternalModel instances, keyed by an associated
|
||||||
|
// model name
|
||||||
|
var ModelRegistry = make(TModelRegistry, 0)
|
||||||
|
|
||||||
|
// DB - The mongodb database handle
|
||||||
|
var DB *mongo.Database
|
||||||
|
|
||||||
|
// DBClient - The mongodb client
|
||||||
|
var DBClient *mongo.Client
|
||||||
|
|
||||||
|
// NextStringID - Override this function with your own
|
||||||
|
// string ID generator!
|
||||||
|
var NextStringID func() string
|
||||||
|
|
||||||
|
var mutex sync.Mutex
|
||||||
|
|
||||||
|
func getRawTypeFromTag(tagOpt string, slice bool) reflect.Type {
|
||||||
|
var t reflect.Type
|
||||||
|
switch strings.ToLower(tagOpt) {
|
||||||
|
case "int":
|
||||||
|
var v int64 = 0
|
||||||
|
t = reflect.TypeOf(v)
|
||||||
|
case "uint":
|
||||||
|
var v uint = 0
|
||||||
|
t = reflect.TypeOf(v)
|
||||||
|
case "string":
|
||||||
|
var v string = "0"
|
||||||
|
t = reflect.TypeOf(v)
|
||||||
|
|
||||||
|
}
|
||||||
|
if slice {
|
||||||
|
return reflect.SliceOf(t)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRef(idx int, modelName string, fieldName string, ht reflect.Type) Reference {
|
||||||
|
if modelName != "" {
|
||||||
|
if ModelRegistry.Index(modelName) != -1 {
|
||||||
|
return Reference{
|
||||||
|
Idx: idx,
|
||||||
|
Model: modelName,
|
||||||
|
HydratedType: ht,
|
||||||
|
Kind: ht.Kind(),
|
||||||
|
Exists: true,
|
||||||
|
FieldName: fieldName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Reference{
|
||||||
|
Idx: idx,
|
||||||
|
Model: modelName,
|
||||||
|
FieldName: fieldName,
|
||||||
|
HydratedType: ht,
|
||||||
|
Kind: ht.Kind(),
|
||||||
|
Exists: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("model name was empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTags(t reflect.Type, v reflect.Value) (map[string][]InternalIndex, map[string]Reference, string) {
|
||||||
|
coll := ""
|
||||||
|
refs := make(map[string]Reference, 0)
|
||||||
|
idcs := make(map[string][]InternalIndex, 0)
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
sft := t.Field(i)
|
||||||
|
ft := sft.Type
|
||||||
|
tags, err := structtag.Parse(string(sft.Tag))
|
||||||
|
panik(err)
|
||||||
|
shouldContinue := true
|
||||||
|
for {
|
||||||
|
if !shouldContinue {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch ft.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
ft = ft.Elem()
|
||||||
|
if _, ok := tags.Get("ref"); ok != nil {
|
||||||
|
if ft.Kind() == reflect.Struct {
|
||||||
|
ii2, rr2, _ := parseTags(ft, reflect.New(ft).Elem())
|
||||||
|
for k, v := range ii2 {
|
||||||
|
idcs[sft.Name+"."+k] = v
|
||||||
|
}
|
||||||
|
for k, v := range rr2 {
|
||||||
|
refs[sft.Name+"."+k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case reflect.Pointer:
|
||||||
|
ft = ft.Elem()
|
||||||
|
fallthrough
|
||||||
|
case reflect.Struct:
|
||||||
|
if ft.ConvertibleTo(reflect.TypeOf(Model{})) {
|
||||||
|
collTag, err := tags.Get("coll")
|
||||||
|
panik(err)
|
||||||
|
coll = collTag.Name
|
||||||
|
idxTag, err := tags.Get("idx")
|
||||||
|
if err == nil {
|
||||||
|
idcs[sft.Type.Name()] = scanIndex(idxTag.Value())
|
||||||
|
}
|
||||||
|
shouldContinue = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if refTag, ok := tags.Get("ref"); ok == nil {
|
||||||
|
// ref:"ModelName,refType"
|
||||||
|
/* if len(refTag.Options) < 1 {
|
||||||
|
panic("no raw type provided for ref")
|
||||||
|
} */
|
||||||
|
sname := sft.Name + "@" + refTag.Name
|
||||||
|
refs[sname] = makeRef(i, refTag.Name, sft.Name, sft.Type)
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
idxTag, err := tags.Get("idx")
|
||||||
|
if err == nil {
|
||||||
|
idcs[sft.Name] = scanIndex(idxTag.Value())
|
||||||
|
}
|
||||||
|
shouldContinue = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idcs, refs, coll
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has returns the model typename and InternalModel instance corresponding
|
||||||
|
// to the argument passed, as well as a boolean indicating whether it
|
||||||
|
// was found. otherwise returns `"", nil, false`
|
||||||
|
func (r TModelRegistry) Has(i interface{}) (string, *InternalModel, bool) {
|
||||||
|
t := reflect.TypeOf(i)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
n := t.Name()
|
||||||
|
if rT, ok := ModelRegistry[n]; ok {
|
||||||
|
return n, rT, true
|
||||||
|
}
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasByName functions almost identically to Has,
|
||||||
|
// except that it takes a string as its argument.
|
||||||
|
func (t TModelRegistry) HasByName(n string) (string, *InternalModel, bool) {
|
||||||
|
if t, ok := ModelRegistry[n]; ok {
|
||||||
|
return n, t, true
|
||||||
|
}
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the index at which the Model struct is embedded
|
||||||
|
func (r TModelRegistry) Index(n string) int {
|
||||||
|
if v, ok := ModelRegistry[n]; ok {
|
||||||
|
return v.Idx
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TModelRegistry) new_(n string) interface{} {
|
||||||
|
if n, m, ok := ModelRegistry.HasByName(n); ok {
|
||||||
|
v := reflect.New(m.Type)
|
||||||
|
df := v.Elem().Field(m.Idx)
|
||||||
|
d := df.Interface().(Model)
|
||||||
|
d.typeName = n
|
||||||
|
df.Set(reflect.ValueOf(d))
|
||||||
|
return v.Interface()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model registers models in the ModelRegistry, where
|
||||||
|
// they can be accessed via a model's struct name
|
||||||
|
func (r TModelRegistry) Model(mdl ...any) {
|
||||||
|
defer mutex.Unlock()
|
||||||
|
mutex.Lock()
|
||||||
|
|
||||||
|
for _, m := range mdl {
|
||||||
|
t := reflect.TypeOf(m)
|
||||||
|
v := reflect.ValueOf(m)
|
||||||
|
vp := v
|
||||||
|
if vp.Kind() != reflect.Ptr {
|
||||||
|
vp = reflect.New(v.Type())
|
||||||
|
vp.Elem().Set(v)
|
||||||
|
}
|
||||||
|
id, ok := vp.Interface().(HasID)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("you MUST implement the HasID interface!!! skipping...\n"))
|
||||||
|
}
|
||||||
|
switch (id).Id().(type) {
|
||||||
|
case int, int64, int32, string, primitive.ObjectID, uint, uint32, uint64:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
log.Printf("invalid ID type specified!!! skipping...\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = reflect.Indirect(reflect.ValueOf(m)).Type()
|
||||||
|
v = reflect.ValueOf(m).Elem()
|
||||||
|
}
|
||||||
|
n := t.Name()
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("Only structs can be passed to this function, silly! (passed type: %s)", n))
|
||||||
|
}
|
||||||
|
idx := -1
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
ft := t.Field(i)
|
||||||
|
if (ft.Type.ConvertibleTo(reflect.TypeOf(Model{}))) {
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx < 0 {
|
||||||
|
panic("A model must embed the Model struct!")
|
||||||
|
}
|
||||||
|
inds, refs, coll := parseTags(t, v)
|
||||||
|
if coll == "" {
|
||||||
|
panic(fmt.Sprintf("a model needs to be given a collection name! (passed type: %s)", n))
|
||||||
|
}
|
||||||
|
ModelRegistry[n] = &InternalModel{
|
||||||
|
Idx: idx,
|
||||||
|
Type: t,
|
||||||
|
Collection: coll,
|
||||||
|
Indexes: inds,
|
||||||
|
References: refs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range ModelRegistry {
|
||||||
|
for k2, v2 := range v.References {
|
||||||
|
if !v2.Exists {
|
||||||
|
if _, ok := ModelRegistry[v2.FieldName]; ok {
|
||||||
|
tmp := ModelRegistry[k].References[k2]
|
||||||
|
ModelRegistry[k].References[k2] = Reference{
|
||||||
|
Model: k,
|
||||||
|
Idx: tmp.Idx,
|
||||||
|
FieldName: tmp.FieldName,
|
||||||
|
Kind: tmp.Kind,
|
||||||
|
HydratedType: tmp.HydratedType,
|
||||||
|
Exists: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerWatch(coll *mongo.Collection) {
|
||||||
|
sspipeline := mongo.Pipeline{
|
||||||
|
bson.D{{"$match", bson.D{{"$or",
|
||||||
|
bson.A{
|
||||||
|
bson.D{{
|
||||||
|
"operationType", "insert",
|
||||||
|
}},
|
||||||
|
bson.D{{"operationType", "update"}},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := coll.Watch(context.TODO(), sspipeline, options.ChangeStream().SetFullDocument(options.UpdateLookup).SetFullDocumentBeforeChange(options.WhenAvailable))
|
||||||
|
panik(err)
|
||||||
|
defer func(stream *mongo.ChangeStream, ctx context2.Context) {
|
||||||
|
err := stream.Close(ctx)
|
||||||
|
panik(err)
|
||||||
|
}(stream, context.TODO())
|
||||||
|
for stream.Next(context.TODO()) {
|
||||||
|
var data bson.M
|
||||||
|
if err := stream.Decode(&data); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var uid = data["documentKey"].(bson.M)["_id"]
|
||||||
|
|
||||||
|
if data["operationType"] == "insert" {
|
||||||
|
counterColl := DB.Collection(COUNTER_COL)
|
||||||
|
counterColl.UpdateOne(context.TODO(), bson.M{"collection": coll.Name()}, bson.M{"$set": bson.M{
|
||||||
|
"current": uid,
|
||||||
|
}}, options.Update().SetUpsert(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%v\n", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Connect(uri string, dbName string) {
|
||||||
|
cli, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("failed to open database")
|
||||||
|
}
|
||||||
|
panik(err)
|
||||||
|
DB = cli.Database(dbName)
|
||||||
|
colls, err := DB.ListCollectionNames(context.TODO(), bson.M{"name": bson.M{"$ne": COUNTER_COL}}, options.ListCollections().SetNameOnly(true))
|
||||||
|
|
||||||
|
for _, c := range colls {
|
||||||
|
if c == COUNTER_COL {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go innerWatch(DB.Collection(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
DBClient = cli
|
||||||
|
}
|
174
testing.go
Normal file
174
testing.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-loremipsum/loremipsum"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type chapter struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id" json:"_id"`
|
||||||
|
Title string `bson:"chapterTitle" json:"chapterTitle" form:"chapterTitle"`
|
||||||
|
ChapterID int `bson:"id" json:"chapterID"`
|
||||||
|
Index int `bson:"index" json:"index" form:"index"`
|
||||||
|
Words int `bson:"words" json:"words"`
|
||||||
|
Notes string `bson:"notes" json:"notes" form:"notes"`
|
||||||
|
Genre []string `bson:"genre" json:"genre" form:"genre"`
|
||||||
|
Bands []band `json:"bands" bson:"bands" ref:"band,bands"`
|
||||||
|
Characters []string `bson:"characters" json:"characters" form:"characters"`
|
||||||
|
Relationships [][]string `bson:"relationships" json:"relationships" form:"relationships"`
|
||||||
|
Adult bool `bson:"adult" json:"adult" form:"adult"`
|
||||||
|
Summary string `bson:"summary" json:"summary" form:"summary"`
|
||||||
|
Hidden bool `bson:"hidden" json:"hidden" form:"hidden"`
|
||||||
|
LoggedInOnly bool `bson:"loggedInOnly" json:"loggedInOnly" form:"loggedInOnly"`
|
||||||
|
Posted time.Time `bson:"datePosted,omitempty" json:"datePosted"`
|
||||||
|
FileName string `json:"fileName" bson:"-"`
|
||||||
|
Text string `json:"text" bson:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type band struct {
|
||||||
|
ID int64 `bson:"_id" json:"_id"`
|
||||||
|
Model `bson:",inline" json:",inline" coll:"bands"`
|
||||||
|
Name string `bson:"name" json:"name" form:"name"`
|
||||||
|
Locked bool `bson:"locked" json:"locked" form:"locked"`
|
||||||
|
Characters []string `bson:"characters" json:"characters" form:"characters"`
|
||||||
|
}
|
||||||
|
type user struct {
|
||||||
|
ID int64 `bson:"_id" json:"_id"`
|
||||||
|
Model `bson:",inline" json:",inline" coll:"users"`
|
||||||
|
Username string `bson:"username" json:"username"`
|
||||||
|
}
|
||||||
|
type story struct {
|
||||||
|
ID int64 `bson:"_id" json:"_id"`
|
||||||
|
Model `bson:",inline" json:",inline" coll:"stories"`
|
||||||
|
Title string `bson:"title" json:"title" form:"title"`
|
||||||
|
Author *user `bson:"author" json:"author" ref:"user"`
|
||||||
|
CoAuthor *user `bson:"coAuthor" json:"coAuthor" ref:"user"`
|
||||||
|
Chapters []chapter `bson:"chapters" json:"chapters"`
|
||||||
|
Recs int `bson:"recs" json:"recs"`
|
||||||
|
Favs int `bson:"favs" json:"favs"`
|
||||||
|
Views int `bson:"views" json:"views"`
|
||||||
|
Completed bool `bson:"completed" json:"completed" form:"completed"`
|
||||||
|
Downloads int `bson:"downloads" json:"downloads"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *story) Id() any {
|
||||||
|
return s.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *band) Id() any {
|
||||||
|
return s.ID
|
||||||
|
}
|
||||||
|
func (s *user) Id() any {
|
||||||
|
return s.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *story) SetId(id any) {
|
||||||
|
s.ID = id.(int64)
|
||||||
|
//var t IModel = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *band) SetId(id any) {
|
||||||
|
s.ID = id.(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *user) SetId(id any) {
|
||||||
|
s.ID = id.(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
var author = user{
|
||||||
|
Username: "tablet.exe",
|
||||||
|
}
|
||||||
|
|
||||||
|
func genChaps(single bool) []chapter {
|
||||||
|
var ret []chapter
|
||||||
|
var ceil int
|
||||||
|
if single {
|
||||||
|
ceil = 1
|
||||||
|
} else {
|
||||||
|
ceil = 5
|
||||||
|
}
|
||||||
|
emptyRel := make([][]string, 0)
|
||||||
|
emptyRel = append(emptyRel, make([]string, 0))
|
||||||
|
relMap := [][][]string{
|
||||||
|
{
|
||||||
|
{"Sean Harris", "Brian Tatler"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"Sean Harris", "Brian Tatler"},
|
||||||
|
{"Duncan Scott", "Colin Kimberley"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"Duncan Scott", "Colin Kimberley"},
|
||||||
|
},
|
||||||
|
emptyRel,
|
||||||
|
{
|
||||||
|
{"Sean Harris", "Colin Kimberley", "Brian Tatler"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var b band = band{
|
||||||
|
Name: "Diamond Head",
|
||||||
|
Locked: false,
|
||||||
|
Characters: []string{"Brian Tatler", "Sean Harris", "Duncan Scott", "Colin Kimberley"},
|
||||||
|
}
|
||||||
|
b.ID = 503
|
||||||
|
for i := 0; i < ceil; i++ {
|
||||||
|
spf := fmt.Sprintf("%d.md", i+1)
|
||||||
|
ret = append(ret, chapter{
|
||||||
|
Title: fmt.Sprintf("-%d-", i+1),
|
||||||
|
Index: int(i + 1),
|
||||||
|
Words: 50,
|
||||||
|
Notes: "notenotenote !!!",
|
||||||
|
Genre: []string{"Slash"},
|
||||||
|
Bands: []band{b},
|
||||||
|
Characters: []string{"Sean Harris", "Brian Tatler", "Duncan Scott", "Colin Kimberley"},
|
||||||
|
Relationships: relMap[i],
|
||||||
|
Adult: true,
|
||||||
|
Summary: loremipsum.New().Paragraph(),
|
||||||
|
Hidden: false,
|
||||||
|
LoggedInOnly: true,
|
||||||
|
FileName: spf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
var iti_single story = story{
|
||||||
|
Title: "title",
|
||||||
|
Completed: true,
|
||||||
|
Chapters: genChaps(true),
|
||||||
|
}
|
||||||
|
|
||||||
|
var iti_multi story = story{
|
||||||
|
Title: "Brian Tatler Fucked and Abused Sean Harris",
|
||||||
|
Completed: false,
|
||||||
|
Chapters: genChaps(false),
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTest() {
|
||||||
|
uri := "mongodb://127.0.0.1:27017"
|
||||||
|
db := "rockfic_ormTest"
|
||||||
|
ic, _ := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
|
||||||
|
colls, _ := ic.Database(db).ListCollectionNames(context.TODO(), bson.M{})
|
||||||
|
if len(colls) < 1 {
|
||||||
|
mdb := ic.Database(db)
|
||||||
|
mdb.CreateCollection(context.TODO(), "bands")
|
||||||
|
mdb.CreateCollection(context.TODO(), "stories")
|
||||||
|
mdb.CreateCollection(context.TODO(), "users")
|
||||||
|
}
|
||||||
|
defer ic.Disconnect(context.TODO())
|
||||||
|
Connect(uri, db)
|
||||||
|
author.ID = 696969
|
||||||
|
ModelRegistry.Model(band{}, user{}, story{})
|
||||||
|
}
|
||||||
|
func after() {
|
||||||
|
err := DBClient.Disconnect(context.TODO())
|
||||||
|
panik(err)
|
||||||
|
}
|
70
util.go
Normal file
70
util.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
func panik(err error) {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameOf(i interface{}) string {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
var n string
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice, reflect.Map:
|
||||||
|
if v.Type().Elem().Kind() == reflect.Pointer {
|
||||||
|
n = v.Type().Elem().Elem().Name()
|
||||||
|
}
|
||||||
|
case reflect.Pointer:
|
||||||
|
n = nameOf(reflect.Indirect(v).Interface())
|
||||||
|
default:
|
||||||
|
n = v.Type().Name()
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueOf(i interface{}) reflect.Value {
|
||||||
|
v := reflect.ValueOf(i)
|
||||||
|
if v.Type().Kind() == reflect.Slice || v.Type().Kind() == reflect.Map {
|
||||||
|
in := v.Type().Elem()
|
||||||
|
switch in.Kind() {
|
||||||
|
case reflect.Pointer:
|
||||||
|
v = reflect.New(in.Elem()).Elem()
|
||||||
|
default:
|
||||||
|
v = reflect.New(in).Elem()
|
||||||
|
}
|
||||||
|
} else if v.Type().Kind() == reflect.Pointer {
|
||||||
|
v = valueOf(reflect.Indirect(v).Interface())
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func iFace(input interface{}) interface{} {
|
||||||
|
return reflect.ValueOf(input).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
func iFaceSlice(input interface{}) []interface{} {
|
||||||
|
ret := make([]interface{}, 0)
|
||||||
|
fv := reflect.ValueOf(input)
|
||||||
|
if fv.Type().Kind() != reflect.Slice {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
for i := 0; i < fv.Len(); i++ {
|
||||||
|
ret = append(ret, fv.Index(i).Interface())
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func coerceInt(input reflect.Value, dst reflect.Value) interface{} {
|
||||||
|
if input.Type().Kind() == reflect.Pointer {
|
||||||
|
input = input.Elem()
|
||||||
|
}
|
||||||
|
if dst.Type().Kind() == reflect.Pointer {
|
||||||
|
dst = dst.Elem()
|
||||||
|
}
|
||||||
|
if input.Type().ConvertibleTo(dst.Type()) {
|
||||||
|
return input.Convert(dst.Type()).Interface()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user