diamond-orm/query.go

639 lines
15 KiB
Go

package orm
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/fatih/structtag"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo"
"reflect"
"strings"
"time"
)
type Query struct {
collection *mongo.Collection
op string
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,
alreadyPopulated map[string]bool,
rcoll string, rawDoc interface{},
curDescent 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 && reflect.ValueOf(src).IsNil() {
return src
}
if rv.Kind() != reflect.Pointer {
rv = reflect.New(rt)
rv.Elem().Set(reflect.ValueOf(src))
}
if srt.Kind() == reflect.Struct && !isObject(rawDoc) {
nrd := ModelRegistry.Get(srt.Name())
if nrd != nil && nrd.collection != rcoll {
q, err := nrd.FindByID(rawDoc)
if err == nil {
rawDoc = q.rawDoc
toPopulate := []string{curDescent}
if asIDoc, ok := rv.Interface().(IDocument); ok {
for k, v := range asIDoc.getPopulated() {
if k != curDescent && v {
toPopulate = append(toPopulate, k)
}
}
}
q.Populate(toPopulate...)
q.Exec(rv.Interface())
src = rv.Interface()
}
}
}
var fieldsMap [3]string
type bsonWhat struct {
What string
}
var isNotStructOrSlice bool
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 fld string
var next string
if len(strings.Split(curDescent, ".")) > 1 {
next = strings.Join(strings.Split(curDescent, ".")[1:], ".")
fld = strings.Split(curDescent, ".")[0]
} else {
fld = curDescent
next = curDescent
}
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
}
if len(rawDoc.(bson.A)) > 0 {
if !isObject(rawDoc.(bson.A)[0]) {
next = curDescent
}
}
for i, el := range rawDoc.(bson.A) {
it := rahh.Index(i)
rel := el
popped := populate(r, alreadyPopulated, rcoll, rel, next, it.Interface())
if pidoc, pok := popped.(IDocument); pok {
pidoc.setRaw(rel)
}
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(fld)
} else {
sf = rv.FieldByName(fld)
}
if rv.Kind() == reflect.Pointer {
rsf = rv.Elem().FieldByName(fld)
} else {
rsf = rv.FieldByName(fld)
}
var ff reflect.StructField
var ok bool
if rt.Kind() == reflect.Pointer {
ff, ok = rt.Elem().FieldByName(fld)
} else {
ff, ok = rt.FieldByName(fld)
}
if ok {
tag, err := structtag.Parse(string(ff.Tag))
if err == nil {
val, err2 := tag.Get("bson")
if err2 == nil && val.Name != "-" {
fttt := ff.Type
if fttt.Kind() == reflect.Pointer || fttt.Kind() == reflect.Slice {
fttt = fttt.Elem()
}
fieldsMap = [3]string{fld, fttt.Name(), val.Name}
}
}
} else {
fmt.Println("todo")
}
intermediate := populate(r, alreadyPopulated, rcoll, dd[fieldsMap[2]], next, sf.Interface())
/*if iidoc, idocOk := intermediate.(IDocument); idocOk {
if (reflect.ValueOf(intermediate).CanAddr() && !reflect.ValueOf(intermediate).IsNil()) || !reflect.ValueOf(intermediate).IsZero() {
iiid, iok := intermediate.(HasID)
if intermediate != nil && iok && !reflect.ValueOf(iiid.Id()).IsZero() {
iidoc.setRaw(dd[fieldsMap[2]])
}
}
}*/
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:
isNotStructOrSlice = true
if r.exists {
tto := r.HydratedType
if tto.Kind() == reflect.Pointer || tto.Kind() == reflect.Slice {
tto = tto.Elem()
}
rawt := ModelRegistry.new_(tto.Name())
t := rawt.(IDocument)
q := bson.M{"_id": rawDoc}
reso := DB.Collection(rcoll).FindOne(context.TODO(), q)
if !errors.Is(reso.Err(), mongo.ErrNoDocuments) {
var anotherMap = make(bson.M)
reso.Decode(&anotherMap)
reflect.ValueOf(t).Elem().Set(reflect.ValueOf(rerere(anotherMap, tto, false)).Elem())
t.setRaw(anotherMap)
}
hatred := rv
if hatred.Kind() == reflect.Pointer {
hatred = hatred.Elem()
}
if hatred.CanSet() {
if reflect.ValueOf(t).Kind() == reflect.Pointer {
if hatred.Kind() == reflect.Pointer {
hatred.Set(reflect.ValueOf(t))
} else {
hatred.Set(reflect.ValueOf(t).Elem())
}
} else {
hatred.Set(reflect.ValueOf(t))
}
} else {
src = t
toReturn = src
}
t.SetSelf(t)
t.setExists(true)
}
}
if toReturn == nil {
sidoc, sok := rv.Interface().(IDocument)
if sok {
sidoc.SetSelf(rv.Interface())
if !isNotStructOrSlice {
sidoc.setRaw(rawDoc)
}
} else if rv.Kind() == reflect.Pointer && rt.Kind() != reflect.Pointer {
rv = rv.Elem()
}
return rv.Interface()
}
sidoc, sok := src.(IDocument)
if sok {
sidoc.SetSelf(src)
if !isNotStructOrSlice {
sidoc.setRaw(rawDoc)
}
}
return src
}
// LoadFile - loads the contents of one or more files
// stored in gridFS into the fields named by `fields`.
//
// gridFS fields can be either a `string` or `[]byte`, and are
// tagged with `gridfs:"BUCKET,FILE_FORMAT`
// where:
// - `BUCKET` is the name of the bucket where the files are stored
// - `FILE_FORMAT` is any valid go template string that resolves to
// the unique file name.
// all exported values and methods present in the surrounding
// struct can be used in this template.
func (q *Query) LoadFile(fields ...string) *Query {
_, cm, _ := ModelRegistry.HasByName(q.model.typeName)
if cm != nil {
for _, field := range fields {
var r gridFSReference
hasAnnotated := false
for k2, v := range cm.gridFSReferences {
if strings.HasPrefix(k2, field) {
r = v
hasAnnotated = true
break
}
}
if hasAnnotated {
q.doc = gridFsLoad(q.doc, r, field)
}
}
}
return q
}
func readFields(field string, m *Model) map[string]Reference {
r := make(map[string]Reference)
if m == nil {
return r
}
for k, v := range m.references {
if strings.HasPrefix(field, k) {
r[k] = v
}
}
if vv, ok := m.references[field]; ok {
r[field] = vv
}
return r
}
// Populate populates document references via reflection
func (q *Query) Populate(fields ...string) *Query {
vvv := reflect.ValueOf(q.doc)
if vvv.Kind() == reflect.Pointer {
vvv = vvv.Elem()
}
if vvv.Kind() == reflect.Slice {
typ := reflect.PointerTo(q.model.Type)
slic := reflect.New(reflect.SliceOf(typ))
for i := 0; i < vvv.Len(); i++ {
val2 := vvv.Index(i).Interface()
aid, docOk := val2.(IDocument)
if docOk {
rdoc := q.rawDoc.(bson.A)
aid.setRaw(rdoc[i])
aid.Populate(fields...)
if reflect.ValueOf(aid).Kind() == reflect.Pointer {
slic.Elem().Set(reflect.Append(slic.Elem(), reflect.ValueOf(aid)))
} else {
slic.Elem().Set(reflect.Append(slic, reflect.ValueOf(aid)))
}
}
}
q.doc = slic.Interface()
} else if asDoc, ok2 := q.doc.(IDocument); ok2 {
asDoc.setRaw(q.rawDoc)
asDoc.Populate(fields...)
}
return q
}
func (q *Query) reOrganize() {
var trvo reflect.Value
if arr, ok := q.rawDoc.(bson.A); ok {
typ := ModelRegistry[q.model.typeName].Type
if typ.Kind() != reflect.Pointer {
typ = reflect.PointerTo(typ)
}
slic := reflect.New(reflect.SliceOf(typ))
for _, v2 := range arr {
inter := reflect.ValueOf(rerere(v2, typ, false))
slic.Elem().Set(reflect.Append(slic.Elem(), inter))
}
trvo = slic.Elem()
} else {
trvo = reflect.ValueOf(rerere(q.rawDoc, reflect.TypeOf(q.doc), false))
}
resV := reflect.ValueOf(q.doc)
for {
if resV.Kind() == reflect.Pointer {
if resV.Elem().Kind() == reflect.Slice {
resV = resV.Elem()
} else {
break
}
} else {
break
}
}
if resV.CanSet() {
resV.Set(trvo)
} else {
resV.Elem().Set(trvo.Elem())
}
}
func rerere(input interface{}, resType reflect.Type, isJson bool) interface{} {
t := reflect.TypeOf(input)
v := reflect.ValueOf(input)
var key string
if isJson {
key = "json"
} else {
key = "bson"
}
if input == nil {
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()
}
var resV reflect.Value
var newInstance interface{}
if _, _, has := ModelRegistry.Has(reflect.New(resType).Elem().Interface()); has {
newInstance = Create(reflect.New(resType).Elem().Interface())
resV = reflect.ValueOf(newInstance)
} else {
newInstance = ModelRegistry.newForType(resType)
if newInstance == nil && resType.Kind() == reflect.Pointer {
newInstance = ModelRegistry.newForType(resType.Elem())
if newInstance == nil {
resV = reflect.New(resType)
} else {
resV = reflect.ValueOf(newInstance)
}
} else if newInstance != nil {
resV = reflect.ValueOf(newInstance)
} else {
resV = reflect.New(resType)
}
}
var rve = 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 {
var omap map[string]interface{}
if omap, ok = v.Interface().(map[string]interface{}); ok {
mipmap = bson.M(omap)
}
}
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(key)
if err != nil {
continue
}
if btag.Name == "-" {
continue
}
intermediate := mipmap[btag.Name]
_, err = tags.Get("ref")
if err != nil {
var tmp interface{}
if ttmp, tok := intermediate.(bson.DateTime); tok {
tmp = ttmp.Time()
} else {
tmp = rerere(intermediate, ft.Type, isJson)
}
fuck := reflect.ValueOf(tmp)
if tmp != nil {
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, isJson)
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))
}
}
}
}
if shouldBreak {
// break
}
} else {
nunu := ModelRegistry.new_(resType.Name())
ider, ok := nunu.(HasID)
if ok {
toConvert := reflect.ValueOf(ider.Id()).Type()
if v.Type() != toConvert {
if v.CanConvert(toConvert) {
ider.SetId(v.Convert(toConvert).Interface())
}
} else {
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:
_, aOk := v.Interface().(bson.A)
var arr []interface{}
if !aOk {
arr = v.Interface().([]interface{})
} else {
arr = []interface{}(v.Interface().(bson.A))
}
for _, it := range arr {
if it != nil {
tmp := reflect.ValueOf(rerere(it, rve.Type().Elem(), isJson))
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[bson.DateTime]() {
fval = fval.(bson.DateTime).Time()
}
if valueField.Kind() == reflect.Pointer {
valueField.Elem().Set(reflect.ValueOf(fval))
} else {
if reflect.TypeOf(fval) == reflect.TypeFor[string]() && typeField.Type == reflect.TypeFor[time.Time]() {
tt, _ := time.Parse(time.RFC3339, fval.(string))
valueField.Set(reflect.ValueOf(tt))
} 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, "", "\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!")
}
doc := reflect.ValueOf(q.doc)
if doc.Elem().Kind() == reflect.Slice {
for i := 0; i < doc.Elem().Len(); i++ {
cur := doc.Elem().Index(i)
imodel, ok := cur.Interface().(IDocument)
if ok {
imodel.setExists(true)
imodel.SetSelf(imodel)
doc.Elem().Index(i).Set(reflect.ValueOf(imodel))
}
}
}
if idoc, ok := q.doc.(IDocument); ok {
idoc.SetSelf(result)
}
if rdoc, ok2 := result.(IDocument); ok2 {
rdoc.SetSelf(result)
}
reflect.ValueOf(result).Elem().Set(reflect.ValueOf(q.doc).Elem())
q.done = true
}