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 }