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  }