github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/collection/cdoc_func.go (about) 1 /* 2 * Copyright (c) 2021-present unTill Pro, Ltd. 3 * 4 * @author Michael Saigachenko 5 */ 6 7 package collection 8 9 import ( 10 "context" 11 "encoding/json" 12 "net/http" 13 "strconv" 14 15 "github.com/voedger/voedger/pkg/appdef" 16 "github.com/voedger/voedger/pkg/istructs" 17 istructsmem "github.com/voedger/voedger/pkg/istructsmem" 18 "github.com/voedger/voedger/pkg/state" 19 coreutils "github.com/voedger/voedger/pkg/utils" 20 ) 21 22 func provideQryCDoc(cfg *istructsmem.AppConfigType) { 23 cfg.Resources.Add(istructsmem.NewQueryFunction( 24 qNameQueryGetCDoc, 25 provideExecQryCDoc(cfg.AppDef))) 26 } 27 28 func provideExecQryCDoc(appDef appdef.IAppDef) istructsmem.ExecQueryClosure { 29 return func(ctx context.Context, args istructs.ExecQueryArgs, callback istructs.ExecQueryCallback) (err error) { 30 rkb, err := args.State.KeyBuilder(state.Record, appdef.NullQName) 31 if err != nil { 32 return 33 } 34 rkb.PutRecordID(state.Field_ID, istructs.RecordID(args.ArgumentObject.AsInt64(field_ID))) 35 rsv, err := args.State.MustExist(rkb) 36 if err != nil { 37 return 38 } 39 40 vrkb, err := args.State.KeyBuilder(state.View, QNameCollectionView) 41 if err != nil { 42 return 43 } 44 vrkb.PutQName(Field_DocQName, rsv.AsQName(appdef.SystemField_QName)) 45 vrkb.PutInt32(Field_PartKey, PartitionKeyCollection) 46 vrkb.PutRecordID(field_DocID, rsv.AsRecordID(appdef.SystemField_ID)) 47 48 var doc *collectionObject 49 50 // build tree 51 err = args.State.Read(vrkb, func(key istructs.IKey, value istructs.IStateValue) (err error) { 52 rec := value.AsRecord(Field_Record) 53 if doc == nil { 54 cobj := newCollectionObject(rec) 55 doc = cobj 56 } else { 57 doc.addRawRecord(rec) 58 } 59 return 60 }) 61 if err != nil { 62 return 63 } 64 65 if doc == nil { 66 return coreutils.NewHTTPErrorf(http.StatusNotFound, "Document not found") 67 } 68 69 doc.handleRawRecords() 70 71 var bytes []byte 72 var obj map[string]interface{} 73 refs := make(map[istructs.RecordID]bool) 74 obj, err = convert(doc, appDef, refs, istructs.NullRecordID) 75 if err != nil { 76 return 77 } 78 err = addRefs(obj, refs, args.State, appDef) 79 if err != nil { 80 return 81 } 82 bytes, err = marshal(obj) 83 if err != nil { 84 return 85 } 86 return callback(&cdocObject{data: string(bytes)}) 87 } 88 } 89 90 func convert(doc istructs.IObject, appDef appdef.IAppDef, refs map[istructs.RecordID]bool, parent istructs.RecordID) (obj map[string]interface{}, err error) { 91 if doc == nil { 92 return nil, nil 93 } 94 // unable to use ObjectToMap because of filter: 95 // field of the root is filtering -> no problem, field of a container is filtering -> `doc` var here ir root, it does not contain fields of container -> panic 96 obj = coreutils.FieldsToMap(doc, appDef, coreutils.Filter(func(fieldName string, kind appdef.DataKind) bool { 97 if skipField(fieldName) { 98 return false 99 } 100 if refs != nil { 101 if kind == appdef.DataKind_RecordID && fieldName != appdef.SystemField_ID { 102 // the field is a reference 103 if parent != doc.AsRecordID(fieldName) { 104 refs[doc.AsRecordID(fieldName)] = true 105 } 106 } 107 } 108 return true 109 })) 110 doc.Containers(func(container string) { 111 list := make([]interface{}, 0) 112 doc.Children(container, func(c istructs.IObject) { 113 var childObj map[string]interface{} 114 if err == nil { 115 childObj, err = convert(c.(*collectionObject), appDef, refs, doc.AsRecord().ID()) 116 if err == nil { 117 list = append(list, childObj) 118 } 119 } 120 }) 121 if container != "" { 122 obj[container] = list 123 } 124 }) 125 126 return obj, nil 127 } 128 func addRefs(obj map[string]interface{}, refs map[istructs.RecordID]bool, s istructs.IState, appDef appdef.IAppDef) error { 129 if len(refs) == 0 { 130 return nil 131 } 132 133 references := make(map[string]map[string]interface{}) 134 for recordId := range refs { 135 if recordId == istructs.NullRecordID { 136 continue 137 } 138 rkb, err := s.KeyBuilder(state.Record, appdef.NullQName) 139 if err != nil { 140 return err 141 } 142 rkb.PutRecordID(state.Field_ID, recordId) 143 144 rkv, err := s.MustExist(rkb) 145 if err != nil { 146 return err 147 } 148 149 recmap, ok := references[rkv.AsQName(appdef.SystemField_QName).String()] 150 if !ok { 151 recmap = make(map[string]interface{}) 152 references[rkv.AsQName(appdef.SystemField_QName).String()] = recmap 153 } 154 recKey := strconv.FormatInt(int64(recordId), DEC) 155 if _, ok := recmap[recKey]; !ok { 156 child := newCollectionObject(rkv.AsRecord("")) 157 obj, err := convert(child, appDef, nil, istructs.NullRecordID) 158 if err != nil { 159 return err 160 } 161 recmap[recKey] = obj 162 } 163 } 164 obj[field_xrefs] = references 165 return nil 166 } 167 func marshal(obj map[string]interface{}) ([]byte, error) { 168 if obj == nil { 169 return nil, nil 170 } 171 return json.Marshal(obj) 172 } 173 174 func skipField(fieldName string) bool { 175 return fieldName == appdef.SystemField_QName || 176 fieldName == appdef.SystemField_Container || 177 fieldName == appdef.SystemField_ParentID 178 179 } 180 181 type cdocObject struct { 182 istructs.NullObject 183 data string 184 } 185 186 func (o cdocObject) AsString(string) string { return o.data }