github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/processors/query/impl.go (about) 1 /* 2 * Copyright (c) 2021-present unTill Pro, Ltd. 3 * 4 * * @author Michael Saigachenko 5 */ 6 7 package queryprocessor 8 9 import ( 10 "context" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "net/http" 15 "runtime/debug" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/voedger/voedger/pkg/appdef" 21 "github.com/voedger/voedger/pkg/appparts" 22 "github.com/voedger/voedger/pkg/iauthnz" 23 "github.com/voedger/voedger/pkg/iprocbus" 24 "github.com/voedger/voedger/pkg/isecrets" 25 "github.com/voedger/voedger/pkg/isecretsimpl" 26 "github.com/voedger/voedger/pkg/istructs" 27 "github.com/voedger/voedger/pkg/itokens" 28 payloads "github.com/voedger/voedger/pkg/itokens-payloads" 29 imetrics "github.com/voedger/voedger/pkg/metrics" 30 "github.com/voedger/voedger/pkg/pipeline" 31 "github.com/voedger/voedger/pkg/processors" 32 "github.com/voedger/voedger/pkg/state" 33 "github.com/voedger/voedger/pkg/sys/authnz" 34 "github.com/voedger/voedger/pkg/utils/federation" 35 ibus "github.com/voedger/voedger/staging/src/github.com/untillpro/airs-ibus" 36 37 coreutils "github.com/voedger/voedger/pkg/utils" 38 ) 39 40 func implRowsProcessorFactory(ctx context.Context, appDef appdef.IAppDef, state istructs.IState, params IQueryParams, 41 resultMeta appdef.IType, rs IResultSenderClosable, metrics IMetrics) pipeline.IAsyncPipeline { 42 operators := make([]*pipeline.WiredOperator, 0) 43 if resultMeta == nil { 44 // happens when the query has no result, e.g. q.air.UpdateSubscriptionDetails 45 operators = append(operators, pipeline.WireAsyncOperator("noop, no result", &pipeline.AsyncNOOP{})) 46 } else if resultMeta.QName() == istructs.QNameRaw { 47 operators = append(operators, pipeline.WireAsyncOperator("Raw result", &RawResultOperator{ 48 metrics: metrics, 49 })) 50 } else { 51 fieldsDefs := &fieldsDefs{ 52 appDef: appDef, 53 fields: make(map[appdef.QName]FieldsKinds), 54 } 55 rootFields := newFieldsKinds(resultMeta) 56 operators = append(operators, pipeline.WireAsyncOperator("Result fields", &ResultFieldsOperator{ 57 elements: params.Elements(), 58 rootFields: rootFields, 59 fieldsDefs: fieldsDefs, 60 metrics: metrics, 61 })) 62 operators = append(operators, pipeline.WireAsyncOperator("Enrichment", &EnrichmentOperator{ 63 state: state, 64 elements: params.Elements(), 65 fieldsDefs: fieldsDefs, 66 metrics: metrics, 67 })) 68 if len(params.Filters()) != 0 { 69 operators = append(operators, pipeline.WireAsyncOperator("Filter", &FilterOperator{ 70 filters: params.Filters(), 71 rootFields: rootFields, 72 metrics: metrics, 73 })) 74 } 75 if len(params.OrderBy()) != 0 { 76 operators = append(operators, pipeline.WireAsyncOperator("Order", newOrderOperator(params.OrderBy(), metrics))) 77 } 78 if params.StartFrom() != 0 || params.Count() != 0 { 79 operators = append(operators, pipeline.WireAsyncOperator("Counter", newCounterOperator( 80 params.StartFrom(), 81 params.Count(), 82 metrics))) 83 } 84 } 85 operators = append(operators, pipeline.WireAsyncOperator("Send to bus", &SendToBusOperator{ 86 rs: rs, 87 metrics: metrics, 88 })) 89 return pipeline.NewAsyncPipeline(ctx, "Rows processor", operators[0], operators[1:]...) 90 } 91 92 func implServiceFactory(serviceChannel iprocbus.ServiceChannel, resultSenderClosableFactory ResultSenderClosableFactory, 93 appParts appparts.IAppPartitions, maxPrepareQueries int, metrics imetrics.IMetrics, vvm string, 94 authn iauthnz.IAuthenticator, authz iauthnz.IAuthorizer, itokens itokens.ITokens, federation federation.IFederation) pipeline.IService { 95 secretReader := isecretsimpl.ProvideSecretReader() 96 return pipeline.NewService(func(ctx context.Context) { 97 var p pipeline.ISyncPipeline 98 for ctx.Err() == nil { 99 select { 100 case intf := <-serviceChannel: 101 now := time.Now() 102 msg := intf.(IQueryMessage) 103 qpm := &queryProcessorMetrics{ 104 vvm: vvm, 105 app: msg.AppQName(), 106 metrics: metrics, 107 } 108 qpm.Increase(queriesTotal, 1.0) 109 rs := resultSenderClosableFactory(msg.RequestCtx(), msg.Sender()) 110 qwork := newQueryWork(msg, rs, appParts, maxPrepareQueries, qpm, secretReader) 111 if p == nil { 112 p = newQueryProcessorPipeline(ctx, authn, authz, itokens, federation) 113 } 114 err := p.SendSync(qwork) 115 if err != nil { 116 qpm.Increase(errorsTotal, 1.0) 117 p.Close() 118 p = nil 119 } else { 120 err = execAndSendResponse(ctx, qwork) 121 } 122 if qwork.rowsProcessor != nil { 123 // wait until all rows are sent 124 qwork.rowsProcessor.Close() 125 } 126 err = coreutils.WrapSysError(err, http.StatusInternalServerError) 127 rs.Close(err) 128 qwork.release() 129 metrics.IncreaseApp(queriesSeconds, vvm, msg.AppQName(), time.Since(now).Seconds()) 130 case <-ctx.Done(): 131 } 132 } 133 if p != nil { 134 p.Close() 135 } 136 }) 137 } 138 139 func execAndSendResponse(ctx context.Context, qw *queryWork) (err error) { 140 now := time.Now() 141 defer func() { 142 if r := recover(); r != nil { 143 stack := string(debug.Stack()) 144 err = fmt.Errorf("%v\n%s", r, stack) 145 } 146 qw.metrics.Increase(execSeconds, time.Since(now).Seconds()) 147 }() 148 return qw.queryExec(ctx, qw.execQueryArgs, qw.callbackFunc) 149 } 150 151 func newQueryProcessorPipeline(requestCtx context.Context, authn iauthnz.IAuthenticator, authz iauthnz.IAuthorizer, 152 itokens itokens.ITokens, federation federation.IFederation) pipeline.ISyncPipeline { 153 ops := []*pipeline.WiredOperator{ 154 operator("borrowAppPart", borrowAppPart), 155 operator("check function call rate", func(ctx context.Context, qw *queryWork) (err error) { 156 if qw.appStructs.IsFunctionRateLimitsExceeded(qw.msg.QName(), qw.msg.WSID()) { 157 return coreutils.NewSysError(http.StatusTooManyRequests) 158 } 159 return nil 160 }), 161 operator("authenticate query request", func(ctx context.Context, qw *queryWork) (err error) { 162 req := iauthnz.AuthnRequest{ 163 Host: qw.msg.Host(), 164 RequestWSID: qw.msg.WSID(), 165 Token: qw.msg.Token(), 166 } 167 if qw.principals, qw.principalPayload, err = authn.Authenticate(qw.msg.RequestCtx(), qw.appStructs, qw.appStructs.AppTokens(), req); err != nil { 168 return coreutils.WrapSysError(err, http.StatusUnauthorized) 169 } 170 return 171 }), 172 operator("get workspace descriptor", func(ctx context.Context, qw *queryWork) (err error) { 173 qw.wsDesc, err = qw.appStructs.Records().GetSingleton(qw.msg.WSID(), authnz.QNameCDocWorkspaceDescriptor) 174 return err 175 }), 176 operator("check cdoc.sys.WorkspaceDescriptor existence", func(ctx context.Context, qw *queryWork) (err error) { 177 if qw.wsDesc.QName() == appdef.NullQName { 178 // TODO: ws init check is simplified here because we need just IWorkspace to get the query from it. 179 return processors.ErrWSNotInited 180 } 181 return nil 182 }), 183 operator("check workspace active", func(ctx context.Context, qw *queryWork) (err error) { 184 for _, prn := range qw.principals { 185 if prn.Kind == iauthnz.PrincipalKind_Role && prn.QName == iauthnz.QNameRoleSystem && prn.WSID == qw.msg.WSID() { 186 // system -> allow to work in any case 187 return nil 188 } 189 } 190 if qw.wsDesc.QName() == appdef.NullQName { 191 // TODO: query prcessor currently does not check the workspace active state 192 return nil 193 } 194 if qw.wsDesc.AsInt32(authnz.Field_Status) != int32(authnz.WorkspaceStatus_Active) { 195 return processors.ErrWSInactive 196 } 197 return nil 198 }), 199 operator("get IWorkspace", func(ctx context.Context, qw *queryWork) (err error) { 200 if qw.wsDesc.QName() == appdef.NullQName { 201 // workspace is dummy 202 return nil 203 } 204 if qw.iWorkspace = qw.appStructs.AppDef().WorkspaceByDescriptor(qw.wsDesc.AsQName(authnz.Field_WSKind)); qw.iWorkspace == nil { 205 return coreutils.NewHTTPErrorf(http.StatusInternalServerError, fmt.Sprintf("workspace is not found in AppDef by cdoc.sys.WorkspaceDescriptor.WSKind %s", 206 qw.wsDesc.AsQName(authnz.Field_WSKind))) 207 } 208 return nil 209 }), 210 operator("get IQuery", func(ctx context.Context, qw *queryWork) (err error) { 211 queryType := qw.iWorkspace.Type(qw.msg.QName()) 212 if queryType.Kind() == appdef.TypeKind_null { 213 return coreutils.NewHTTPErrorf(http.StatusBadRequest, fmt.Sprintf("query %s does not exist in workspace %s", qw.msg.QName(), qw.iWorkspace.QName())) 214 } 215 ok := false 216 if qw.iQuery, ok = queryType.(appdef.IQuery); !ok { 217 return coreutils.NewHTTPErrorf(http.StatusBadRequest, fmt.Sprintf("%s is not a query", qw.msg.QName())) 218 } 219 return nil 220 }), 221 222 operator("authorize query request", func(ctx context.Context, qw *queryWork) (err error) { 223 req := iauthnz.AuthzRequest{ 224 OperationKind: iauthnz.OperationKind_EXECUTE, 225 Resource: qw.msg.QName(), 226 } 227 ok, err := authz.Authorize(qw.appStructs, qw.principals, req) 228 if err != nil { 229 return err 230 } 231 if !ok { 232 return coreutils.WrapSysError(errors.New(""), http.StatusForbidden) 233 } 234 return nil 235 }), 236 operator("unmarshal request", func(ctx context.Context, qw *queryWork) (err error) { 237 parsType := qw.iQuery.Param() 238 if parsType != nil && parsType.QName() == istructs.QNameRaw { 239 qw.requestData["args"] = map[string]interface{}{ 240 processors.Field_RawObject_Body: string(qw.msg.Body()), 241 } 242 return nil 243 } 244 err = json.Unmarshal(qw.msg.Body(), &qw.requestData) 245 return coreutils.WrapSysError(err, http.StatusBadRequest) 246 }), 247 operator("validate: get exec query args", func(ctx context.Context, qw *queryWork) (err error) { 248 qw.execQueryArgs, err = newExecQueryArgs(qw.requestData, qw.msg.WSID(), qw) 249 return coreutils.WrapSysError(err, http.StatusBadRequest) 250 }), 251 operator("create callback func", func(ctx context.Context, qw *queryWork) (err error) { 252 qw.callbackFunc = func(object istructs.IObject) error { 253 pathToIdx := make(map[string]int) 254 if qw.resultType.QName() == istructs.QNameRaw { 255 pathToIdx[processors.Field_RawObject_Body] = 0 256 } else { 257 for i, element := range qw.queryParams.Elements() { 258 pathToIdx[element.Path().Name()] = i 259 } 260 } 261 return qw.rowsProcessor.SendAsync(rowsWorkpiece{ 262 object: object, 263 outputRow: &outputRow{ 264 keyToIdx: pathToIdx, 265 values: make([]interface{}, len(pathToIdx)), 266 }, 267 enrichedRootFieldsKinds: make(map[string]appdef.DataKind), 268 }) 269 } 270 return nil 271 }), 272 operator("create state", func(ctx context.Context, qw *queryWork) (err error) { 273 qw.state = state.ProvideQueryProcessorStateFactory()( 274 qw.msg.RequestCtx(), 275 func() istructs.IAppStructs { return qw.appStructs }, 276 state.SimplePartitionIDFunc(qw.msg.Partition()), 277 state.SimpleWSIDFunc(qw.msg.WSID()), 278 qw.secretReader, 279 func() []iauthnz.Principal { return qw.principals }, 280 func() string { return qw.msg.Token() }, 281 itokens, 282 func() istructs.IObject { return qw.execQueryArgs.ArgumentObject }, 283 func() istructs.IObjectBuilder { 284 return qw.appStructs.ObjectBuilder(qw.resultType.QName()) 285 }, 286 federation, 287 func() istructs.ExecQueryCallback { 288 return qw.callbackFunc 289 }, 290 ) 291 qw.execQueryArgs.State = qw.state 292 return 293 }), 294 operator("get queryFunc", func(ctx context.Context, qw *queryWork) (err error) { 295 qw.queryFunc = qw.appStructs.Resources().QueryResource(qw.msg.QName()).(istructs.IQueryFunction) 296 return nil 297 }), 298 operator("validate: get result type", func(ctx context.Context, qw *queryWork) (err error) { 299 qw.resultType = qw.iQuery.Result() 300 if qw.resultType == nil { 301 return nil 302 } 303 if qw.resultType.QName() == appdef.QNameANY { 304 qNameResultType := qw.queryFunc.ResultType(qw.execQueryArgs.PrepareArgs) 305 qw.resultType = qw.iWorkspace.Type(qNameResultType) 306 if qw.resultType.Kind() == appdef.TypeKind_null { 307 return coreutils.NewHTTPError(http.StatusBadRequest, fmt.Errorf("%s query result type %s does not exist in workspace %s", qw.iQuery.QName(), qNameResultType, qw.iWorkspace.QName())) 308 } 309 } 310 return nil 311 }), 312 operator("validate: get query params", func(ctx context.Context, qw *queryWork) (err error) { 313 qw.queryParams, err = newQueryParams(qw.requestData, NewElement, NewFilter, NewOrderBy, newFieldsKinds(qw.resultType)) 314 return coreutils.WrapSysError(err, http.StatusBadRequest) 315 }), 316 operator("authorize result", func(ctx context.Context, qw *queryWork) (err error) { 317 req := iauthnz.AuthzRequest{ 318 OperationKind: iauthnz.OperationKind_SELECT, 319 Resource: qw.msg.QName(), 320 } 321 for _, elem := range qw.queryParams.Elements() { 322 for _, resultField := range elem.ResultFields() { 323 req.Fields = append(req.Fields, resultField.Field()) 324 } 325 } 326 if len(req.Fields) == 0 { 327 return nil 328 } 329 ok, err := authz.Authorize(qw.appStructs, qw.principals, req) 330 if err != nil { 331 return err 332 } 333 if !ok { 334 return coreutils.NewSysError(http.StatusForbidden) 335 } 336 return nil 337 }), 338 operator("build rows processor", func(ctx context.Context, qw *queryWork) error { 339 now := time.Now() 340 defer func() { 341 qw.metrics.Increase(buildSeconds, time.Since(now).Seconds()) 342 }() 343 qw.rowsProcessor = ProvideRowsProcessorFactory()(qw.msg.RequestCtx(), qw.appStructs.AppDef(), 344 qw.state, qw.queryParams, qw.resultType, qw.rs, qw.metrics) 345 return nil 346 }), 347 operator("get func exec", func(ctx context.Context, qw *queryWork) (err error) { 348 iResource := qw.appStructs.Resources().QueryResource(qw.iQuery.QName()) 349 iQueryFunc := iResource.(istructs.IQueryFunction) 350 qw.queryExec = iQueryFunc.Exec 351 return nil 352 }), 353 } 354 return pipeline.NewSyncPipeline(requestCtx, "Query Processor", ops[0], ops[1:]...) 355 } 356 357 type queryWork struct { 358 // input 359 msg IQueryMessage 360 rs IResultSenderClosable 361 appParts appparts.IAppPartitions 362 // work 363 requestData map[string]interface{} 364 state istructs.IState 365 queryParams IQueryParams 366 appPart appparts.IAppPartition 367 appStructs istructs.IAppStructs 368 resultType appdef.IType 369 execQueryArgs istructs.ExecQueryArgs 370 maxPrepareQueries int 371 rowsProcessor pipeline.IAsyncPipeline 372 metrics IMetrics 373 principals []iauthnz.Principal 374 principalPayload payloads.PrincipalPayload 375 secretReader isecrets.ISecretReader 376 queryFunc istructs.IQueryFunction 377 iWorkspace appdef.IWorkspace 378 iQuery appdef.IQuery 379 wsDesc istructs.IRecord 380 queryExec func(ctx context.Context, args istructs.ExecQueryArgs, callback istructs.ExecQueryCallback) error 381 callbackFunc istructs.ExecQueryCallback 382 } 383 384 func newQueryWork(msg IQueryMessage, rs IResultSenderClosable, appParts appparts.IAppPartitions, 385 maxPrepareQueries int, metrics *queryProcessorMetrics, secretReader isecrets.ISecretReader) *queryWork { 386 return &queryWork{ 387 msg: msg, 388 rs: rs, 389 appParts: appParts, 390 requestData: make(map[string]interface{}), 391 maxPrepareQueries: maxPrepareQueries, 392 metrics: metrics, 393 secretReader: secretReader, 394 } 395 } 396 397 // need for q.sys.EnrichPrincipalToken 398 func (qw *queryWork) GetPrincipals() []iauthnz.Principal { 399 return qw.principals 400 } 401 402 // borrows app partition for query 403 func (qw *queryWork) borrow() (err error) { 404 if qw.appPart, err = qw.appParts.Borrow(qw.msg.AppQName(), qw.msg.Partition(), appparts.ProcessorKind_Query); err != nil { 405 return err 406 } 407 qw.appStructs = qw.appPart.AppStructs() 408 return nil 409 } 410 411 // releases borrowed app partition 412 func (qw *queryWork) release() { 413 if ap := qw.appPart; ap != nil { 414 qw.appStructs = nil 415 qw.appPart = nil 416 ap.Release() 417 } 418 } 419 420 // need or q.sys.EnrichPrincipalToken 421 func (qw *queryWork) AppQName() istructs.AppQName { 422 return qw.msg.AppQName() 423 } 424 425 // need for sqlquery 426 func (qw *queryWork) AppPartitions() appparts.IAppPartitions { 427 return qw.appParts 428 } 429 430 func borrowAppPart(_ context.Context, qw *queryWork) error { 431 switch err := qw.borrow(); { 432 case err == nil: 433 return nil 434 case errors.Is(err, appparts.ErrNotAvailableEngines), errors.Is(err, appparts.ErrNotFound): // partition is not deployed yet -> ErrNotFound 435 return coreutils.WrapSysError(err, http.StatusServiceUnavailable) 436 default: 437 return coreutils.WrapSysError(err, http.StatusBadRequest) 438 } 439 } 440 441 func operator(name string, doSync func(ctx context.Context, qw *queryWork) (err error)) *pipeline.WiredOperator { 442 return pipeline.WireFunc(name, func(ctx context.Context, work interface{}) (err error) { 443 return doSync(ctx, work.(*queryWork)) 444 }) 445 } 446 447 type queryMessage struct { 448 requestCtx context.Context 449 appQName istructs.AppQName 450 wsid istructs.WSID 451 partition istructs.PartitionID 452 sender ibus.ISender 453 body []byte 454 qName appdef.QName 455 host string 456 token string 457 } 458 459 func (m queryMessage) AppQName() istructs.AppQName { return m.appQName } 460 func (m queryMessage) WSID() istructs.WSID { return m.wsid } 461 func (m queryMessage) Sender() ibus.ISender { return m.sender } 462 func (m queryMessage) RequestCtx() context.Context { return m.requestCtx } 463 func (m queryMessage) QName() appdef.QName { return m.qName } 464 func (m queryMessage) Host() string { return m.host } 465 func (m queryMessage) Token() string { return m.token } 466 func (m queryMessage) Partition() istructs.PartitionID { return m.partition } 467 func (m queryMessage) Body() []byte { 468 if len(m.body) != 0 { 469 return m.body 470 } 471 return []byte("{}") 472 } 473 474 func NewQueryMessage(requestCtx context.Context, appQName istructs.AppQName, partID istructs.PartitionID, wsid istructs.WSID, sender ibus.ISender, body []byte, 475 qName appdef.QName, host string, token string) IQueryMessage { 476 return queryMessage{ 477 appQName: appQName, 478 wsid: wsid, 479 partition: partID, 480 sender: sender, 481 body: body, 482 requestCtx: requestCtx, 483 qName: qName, 484 host: host, 485 token: token, 486 } 487 } 488 489 type rowsWorkpiece struct { 490 pipeline.IWorkpiece 491 object istructs.IObject 492 outputRow IOutputRow 493 enrichedRootFieldsKinds FieldsKinds 494 } 495 496 func (w rowsWorkpiece) Object() istructs.IObject { return w.object } 497 func (w rowsWorkpiece) OutputRow() IOutputRow { return w.outputRow } 498 func (w rowsWorkpiece) EnrichedRootFieldsKinds() FieldsKinds { return w.enrichedRootFieldsKinds } 499 func (w rowsWorkpiece) PutEnrichedRootFieldKind(name string, kind appdef.DataKind) { 500 w.enrichedRootFieldsKinds[name] = kind 501 } 502 func (w rowsWorkpiece) Release() { 503 // TODO implement it someday 504 // Release goes here 505 } 506 507 type outputRow struct { 508 keyToIdx map[string]int 509 values []interface{} 510 } 511 512 func (r *outputRow) Set(alias string, value interface{}) { r.values[r.keyToIdx[alias]] = value } 513 func (r *outputRow) Values() []interface{} { return r.values } 514 func (r *outputRow) Value(alias string) interface{} { return r.values[r.keyToIdx[alias]] } 515 func (r *outputRow) MarshalJSON() ([]byte, error) { return json.Marshal(r.values) } 516 517 func newExecQueryArgs(data coreutils.MapObject, wsid istructs.WSID, qw *queryWork) (execQueryArgs istructs.ExecQueryArgs, err error) { 518 args, _, err := data.AsObject("args") 519 if err != nil { 520 return execQueryArgs, err 521 } 522 argsType := qw.iQuery.Param() 523 requestArgs := istructs.NewNullObject() 524 if argsType != nil { 525 requestArgsBuilder := qw.appStructs.ObjectBuilder(argsType.QName()) 526 requestArgsBuilder.FillFromJSON(args) 527 requestArgs, err = requestArgsBuilder.Build() 528 if err != nil { 529 return execQueryArgs, err 530 } 531 } 532 return istructs.ExecQueryArgs{ 533 PrepareArgs: istructs.PrepareArgs{ 534 ArgumentObject: requestArgs, 535 WSID: wsid, 536 Workpiece: qw, 537 Workspace: qw.iWorkspace, 538 }, 539 }, nil 540 } 541 542 type path []string 543 544 func (p path) IsRoot() bool { return p[0] == rootDocument } 545 func (p path) Name() string { return strings.Join(p, "/") } 546 func (p path) AsArray() []string { return p } 547 548 type element struct { 549 path path 550 fields []IResultField 551 refs []IRefField 552 } 553 554 func (e element) NewOutputRow() IOutputRow { 555 fields := make([]string, 0) 556 for _, field := range e.fields { 557 fields = append(fields, field.Field()) 558 } 559 for _, field := range e.refs { 560 fields = append(fields, field.Key()) 561 } 562 fieldToIdx := make(map[string]int) 563 for j, field := range fields { 564 fieldToIdx[field] = j 565 } 566 return &outputRow{ 567 keyToIdx: fieldToIdx, 568 values: make([]interface{}, len(fieldToIdx)), 569 } 570 } 571 572 func (e element) Path() IPath { return e.path } 573 func (e element) ResultFields() []IResultField { return e.fields } 574 func (e element) RefFields() []IRefField { return e.refs } 575 576 type fieldsDefs struct { 577 appDef appdef.IAppDef 578 fields map[appdef.QName]FieldsKinds 579 lock sync.Mutex 580 } 581 582 func newFieldsDefs(appDef appdef.IAppDef) *fieldsDefs { 583 return &fieldsDefs{ 584 appDef: appDef, 585 fields: make(map[appdef.QName]FieldsKinds), 586 } 587 } 588 589 func (c *fieldsDefs) get(name appdef.QName) FieldsKinds { 590 c.lock.Lock() 591 defer c.lock.Unlock() 592 fd, ok := c.fields[name] 593 if !ok { 594 fd = newFieldsKinds(c.appDef.Type(name)) 595 c.fields[name] = fd 596 } 597 return fd 598 } 599 600 type queryProcessorMetrics struct { 601 vvm string 602 app istructs.AppQName 603 metrics imetrics.IMetrics 604 } 605 606 func (m *queryProcessorMetrics) Increase(metricName string, valueDelta float64) { 607 m.metrics.IncreaseApp(metricName, m.vvm, m.app, valueDelta) 608 } 609 610 func newFieldsKinds(t appdef.IType) FieldsKinds { 611 res := FieldsKinds{} 612 if fields, ok := t.(appdef.IFields); ok { 613 for _, f := range fields.Fields() { 614 res[f.Name()] = f.DataKind() 615 } 616 } 617 return res 618 }