/// <summary>
        /// Returns an instance to handle the aggregation required by the aggregation expression nodes, depending on
        /// whether there are any group-by nodes.
        /// </summary>
        /// <param name="selectAggregateExprNodes">aggregation nodes extracted out of the select expression</param>
        /// <param name="selectClauseNamedNodes">The select clause named nodes.</param>
        /// <param name="declaredExpressions">The declared expressions.</param>
        /// <param name="groupByNodes">The group by nodes.</param>
        /// <param name="havingAggregateExprNodes">aggregation nodes extracted out of the select expression</param>
        /// <param name="orderByAggregateExprNodes">aggregation nodes extracted out of the select expression</param>
        /// <param name="groupKeyExpressions">The group key expressions.</param>
        /// <param name="hasGroupByClause">indicator on whethere there is group-by required, or group-all</param>
        /// <param name="annotations">statement annotations</param>
        /// <param name="variableService">variable</param>
        /// <param name="isJoin">true for joins</param>
        /// <param name="isDisallowNoReclaim">if set to <c>true</c> [is disallow no reclaim].</param>
        /// <param name="whereClause">the where-clause function if any</param>
        /// <param name="havingClause">the having-clause function if any</param>
        /// <param name="factoryService">The factory service.</param>
        /// <param name="typesPerStream">The types per stream.</param>
        /// <param name="methodResolutionService">The method resolution service.</param>
        /// <param name="groupByRollupDesc">The group by rollup desc.</param>
        /// <param name="optionalContextName">Name of the optional context.</param>
        /// <param name="intoTableSpec">The into table spec.</param>
        /// <param name="tableService">The table service.</param>
        /// <param name="isUnidirectional">if set to <c>true</c> [is unidirectional].</param>
        /// <param name="isFireAndForget">if set to <c>true</c> [is fire and forget].</param>
        /// <param name="isOnSelect">if set to <c>true</c> [is on select].</param>
        /// <returns>
        /// instance for aggregation handling
        /// </returns>
        /// <exception cref="ExprValidationException">
        /// Into-table requires at least one aggregation function
        /// or
        /// The ' + funcname + ' function may not occur in the where-clause or having-clause of a statement with aggregations as 'previous' does not provide remove stream data; Use the 'first','last','window' or 'count' aggregation functions instead
        /// or
        /// Invalid into-table clause: Failed to find table by name ' + intoTableSpec.Name + '
        /// </exception>
        /// <exception cref="EPException">Failed to obtain hook for  + HookType.INTERNAL_AGGLOCALLEVEL</exception>
        /// <throws>com.espertech.esper.epl.expression.core.ExprValidationException if validation fails</throws>
        public static AggregationServiceFactoryDesc GetService(
            IList <ExprAggregateNode> selectAggregateExprNodes,
            IDictionary <ExprNode, string> selectClauseNamedNodes,
            IList <ExprDeclaredNode> declaredExpressions,
            ExprNode[] groupByNodes,
            IList <ExprAggregateNode> havingAggregateExprNodes,
            IList <ExprAggregateNode> orderByAggregateExprNodes,
            IList <ExprAggregateNodeGroupKey> groupKeyExpressions,
            bool hasGroupByClause,
            Attribute[] annotations,
            VariableService variableService,
            bool isJoin,
            bool isDisallowNoReclaim,
            ExprNode whereClause,
            ExprNode havingClause,
            AggregationServiceFactoryService factoryService,
            EventType[] typesPerStream,
            MethodResolutionService methodResolutionService,
            AggregationGroupByRollupDesc groupByRollupDesc,
            string optionalContextName,
            IntoTableSpec intoTableSpec,
            TableService tableService,
            bool isUnidirectional,
            bool isFireAndForget,
            bool isOnSelect)
        {
            // No aggregates used, we do not need this service
            if ((selectAggregateExprNodes.IsEmpty()) && (havingAggregateExprNodes.IsEmpty()))
            {
                if (intoTableSpec != null)
                {
                    throw new ExprValidationException("Into-table requires at least one aggregation function");
                }

                return(new AggregationServiceFactoryDesc(
                           factoryService.GetNullAggregationService(),
                           Collections.GetEmptyList <AggregationServiceAggExpressionDesc>(),
                           Collections.GetEmptyList <ExprAggregateNodeGroupKey>()));
            }

            // Validate the absence of "prev" function in where-clause:
            // Since the "previous" function does not post remove stream results, disallow when used with aggregations.
            if ((whereClause != null) || (havingClause != null))
            {
                var visitor = new ExprNodePreviousVisitorWParent();
                if (whereClause != null)
                {
                    whereClause.Accept(visitor);
                }
                if (havingClause != null)
                {
                    havingClause.Accept(visitor);
                }
                if ((visitor.Previous != null) && (!visitor.Previous.IsEmpty()))
                {
                    string funcname = visitor.Previous[0].Second.PreviousType.ToString().ToLower();
                    throw new ExprValidationException("The '" + funcname + "' function may not occur in the where-clause or having-clause of a statement with aggregations as 'previous' does not provide remove stream data; Use the 'first','last','window' or 'count' aggregation functions instead");
                }
            }

            // Compile a map of aggregation nodes and equivalent-to aggregation nodes.
            // Equivalent-to functions are for example "select sum(a*b), 5*sum(a*b)".
            // Reducing the total number of aggregation functions.
            IList <AggregationServiceAggExpressionDesc> aggregations = new List <AggregationServiceAggExpressionDesc>();

            foreach (var selectAggNode in selectAggregateExprNodes)
            {
                AddEquivalent(selectAggNode, aggregations);
            }
            foreach (var havingAggNode in havingAggregateExprNodes)
            {
                AddEquivalent(havingAggNode, aggregations);
            }
            foreach (var orderByAggNode in orderByAggregateExprNodes)
            {
                AddEquivalent(orderByAggNode, aggregations);
            }

            // Construct a list of evaluation node for the aggregation functions (regular agg).
            // For example "sum(2 * 3)" would make the sum an evaluation node.
            IList <ExprEvaluator> methodAggEvaluatorsList = new List <ExprEvaluator>();

            foreach (var aggregation in aggregations)
            {
                var aggregateNode = aggregation.AggregationNode;
                if (!aggregateNode.Factory.IsAccessAggregation)
                {
                    var evaluator = aggregateNode.Factory.GetMethodAggregationEvaluator(
                        typesPerStream.Length > 1, typesPerStream);
                    methodAggEvaluatorsList.Add(evaluator);
                }
            }

            // determine local group-by, report when hook provided
            AggregationGroupByLocalGroupDesc localGroupDesc = AnalyzeLocalGroupBy(aggregations, groupByNodes, groupByRollupDesc, intoTableSpec);

            // determine binding
            if (intoTableSpec != null)
            {
                // obtain metadata
                var metadata = tableService.GetTableMetadata(intoTableSpec.Name);
                if (metadata == null)
                {
                    throw new ExprValidationException("Invalid into-table clause: Failed to find table by name '" + intoTableSpec.Name + "'");
                }

                EPLValidationUtil.ValidateContextName(true, intoTableSpec.Name, metadata.ContextName, optionalContextName, false);

                // validate group keys
                var groupByTypes = ExprNodeUtility.GetExprResultTypes(groupByNodes);
                ExprTableNodeUtil.ValidateExpressions(intoTableSpec.Name, groupByTypes, "group-by", groupByNodes,
                                                      metadata.KeyTypes, "group-by");

                // determine how this binds to existing aggregations, assign column numbers
                var bindingMatchResult = MatchBindingsAssignColumnNumbers(intoTableSpec, metadata, aggregations, selectClauseNamedNodes, methodAggEvaluatorsList, declaredExpressions);

                // return factory
                AggregationServiceFactory aggregationServiceFactory;
                if (!hasGroupByClause)
                {
                    aggregationServiceFactory = factoryService.GetNoGroupWBinding(bindingMatchResult.Accessors, isJoin, bindingMatchResult.MethodPairs, intoTableSpec.Name, bindingMatchResult.TargetStates, bindingMatchResult.AccessStateExpr, bindingMatchResult.Agents);
                }
                else
                {
                    aggregationServiceFactory = factoryService.GetGroupWBinding(metadata, bindingMatchResult.MethodPairs, bindingMatchResult.Accessors, isJoin, intoTableSpec, bindingMatchResult.TargetStates, bindingMatchResult.AccessStateExpr, bindingMatchResult.Agents, groupByRollupDesc);
                }
                return(new AggregationServiceFactoryDesc(aggregationServiceFactory, aggregations, groupKeyExpressions));
            }

            // Assign a column number to each aggregation node. The regular aggregation goes first followed by access-aggregation.
            var columnNumber = 0;

            foreach (var entry in aggregations)
            {
                if (!entry.Factory.IsAccessAggregation)
                {
                    entry.ColumnNum = columnNumber++;
                }
            }
            foreach (var entry in aggregations)
            {
                if (entry.Factory.IsAccessAggregation)
                {
                    entry.ColumnNum = columnNumber++;
                }
            }

            // determine method aggregation factories and evaluators(non-access)
            ExprEvaluator[] methodAggEvaluators = methodAggEvaluatorsList.ToArray();
            var             methodAggFactories  = new AggregationMethodFactory[methodAggEvaluators.Length];
            var             count = 0;

            foreach (var aggregation in aggregations)
            {
                var aggregateNode = aggregation.AggregationNode;
                if (!aggregateNode.Factory.IsAccessAggregation)
                {
                    methodAggFactories[count] = aggregateNode.Factory;
                    count++;
                }
            }

            // handle access aggregations
            var multiFunctionAggPlan = AggregationMultiFunctionAnalysisHelper.AnalyzeAccessAggregations(aggregations);
            var accessorPairs        = multiFunctionAggPlan.AccessorPairs;
            var accessAggregations   = multiFunctionAggPlan.StateFactories;

            AggregationServiceFactory serviceFactory;

            // analyze local group by
            AggregationLocalGroupByPlan localGroupByPlan = null;

            if (localGroupDesc != null)
            {
                localGroupByPlan = AggregationGroupByLocalGroupByAnalyzer.Analyze(methodAggEvaluators, methodAggFactories, accessAggregations, localGroupDesc, groupByNodes, accessorPairs);
                try {
                    AggregationLocalLevelHook hook = (AggregationLocalLevelHook)TypeHelper.GetAnnotationHook(annotations, HookType.INTERNAL_AGGLOCALLEVEL, typeof(AggregationLocalLevelHook), null);
                    if (hook != null)
                    {
                        hook.Planned(localGroupDesc, localGroupByPlan);
                    }
                }
                catch (ExprValidationException e) {
                    throw new EPException("Failed to obtain hook for " + HookType.INTERNAL_AGGLOCALLEVEL);
                }
            }

            // Handle without a group-by clause: we group all into the same pot
            if (!hasGroupByClause)
            {
                if (localGroupByPlan != null)
                {
                    var groupKeyBinding = methodResolutionService.GetGroupKeyBinding(localGroupByPlan);
                    serviceFactory = factoryService.GetNoGroupLocalGroupBy(isJoin, localGroupByPlan, groupKeyBinding, isUnidirectional, isFireAndForget, isOnSelect);
                }
                else if ((methodAggEvaluators.Length > 0) && (accessorPairs.Length == 0))
                {
                    serviceFactory = factoryService.GetNoGroupNoAccess(methodAggEvaluators, methodAggFactories, isUnidirectional, isFireAndForget, isOnSelect);
                }
                else if ((methodAggEvaluators.Length == 0) && (accessorPairs.Length > 0))
                {
                    serviceFactory = factoryService.GetNoGroupAccessOnly(accessorPairs, accessAggregations, isJoin, isUnidirectional, isFireAndForget, isOnSelect);
                }
                else
                {
                    serviceFactory = factoryService.GetNoGroupAccessMixed(methodAggEvaluators, methodAggFactories, accessorPairs, accessAggregations, isJoin, isUnidirectional, isFireAndForget, isOnSelect);
                }
            }
            else
            {
                var hasNoReclaim          = HintEnum.DISABLE_RECLAIM_GROUP.GetHint(annotations) != null;
                var reclaimGroupAged      = HintEnum.RECLAIM_GROUP_AGED.GetHint(annotations);
                var reclaimGroupFrequency = HintEnum.RECLAIM_GROUP_AGED.GetHint(annotations);
                if (localGroupByPlan != null)
                {
                    var groupKeyBinding = methodResolutionService.GetGroupKeyBinding(localGroupByPlan);
                    serviceFactory = factoryService.GetGroupLocalGroupBy(isJoin, localGroupByPlan, groupKeyBinding, isUnidirectional, isFireAndForget, isOnSelect);
                }
                else
                {
                    var groupKeyBinding = methodResolutionService.GetGroupKeyBinding(groupByNodes, groupByRollupDesc);
                    if (!isDisallowNoReclaim && hasNoReclaim)
                    {
                        if (groupByRollupDesc != null)
                        {
                            throw GetRollupReclaimEx();
                        }
                        if ((methodAggEvaluators.Length > 0) && (accessorPairs.Length == 0))
                        {
                            serviceFactory = factoryService.GetGroupedNoReclaimNoAccess(methodAggEvaluators, methodAggFactories, groupKeyBinding, isUnidirectional, isFireAndForget, isOnSelect);
                        }
                        else if ((methodAggEvaluators.Length == 0) && (accessorPairs.Length > 0))
                        {
                            serviceFactory = factoryService.GetGroupNoReclaimAccessOnly(accessorPairs, accessAggregations, groupKeyBinding, isJoin, isUnidirectional, isFireAndForget, isOnSelect);
                        }
                        else
                        {
                            serviceFactory = factoryService.GetGroupNoReclaimMixed(methodAggEvaluators, methodAggFactories, accessorPairs, accessAggregations, isJoin, groupKeyBinding, isUnidirectional, isFireAndForget, isOnSelect);
                        }
                    }
                    else if (!isDisallowNoReclaim && reclaimGroupAged != null)
                    {
                        if (groupByRollupDesc != null)
                        {
                            throw GetRollupReclaimEx();
                        }
                        serviceFactory = factoryService.GetGroupReclaimAged(methodAggEvaluators, methodAggFactories, reclaimGroupAged, reclaimGroupFrequency, variableService, accessorPairs, accessAggregations, isJoin, groupKeyBinding, optionalContextName, isUnidirectional, isFireAndForget, isOnSelect);
                    }
                    else if (groupByRollupDesc != null)
                    {
                        serviceFactory = factoryService.GetGroupReclaimMixableRollup(methodAggEvaluators, methodAggFactories, accessorPairs, accessAggregations, isJoin, groupKeyBinding, groupByRollupDesc, isUnidirectional, isFireAndForget, isOnSelect);
                    }
                    else
                    {
                        if ((methodAggEvaluators.Length > 0) && (accessorPairs.Length == 0))
                        {
                            serviceFactory = factoryService.GetGroupReclaimNoAccess(methodAggEvaluators, methodAggFactories, accessorPairs, accessAggregations, isJoin, groupKeyBinding, isUnidirectional, isFireAndForget, isOnSelect);
                        }
                        else
                        {
                            serviceFactory = factoryService.GetGroupReclaimMixable(methodAggEvaluators, methodAggFactories, accessorPairs, accessAggregations, isJoin, groupKeyBinding, isUnidirectional, isFireAndForget, isOnSelect);
                        }
                    }
                }
            }

            return(new AggregationServiceFactoryDesc(serviceFactory, aggregations, groupKeyExpressions));
        }
Esempio n. 2
0
        public static AggregationServiceForgeDesc GetService(
            IList<ExprAggregateNode> selectAggregateExprNodes,
            IDictionary<ExprNode, string> selectClauseNamedNodes,
            IList<ExprDeclaredNode> declaredExpressions,
            ExprNode[] groupByNodes,
            MultiKeyClassRef groupByMultiKey,
            IList<ExprAggregateNode> havingAggregateExprNodes,
            IList<ExprAggregateNode> orderByAggregateExprNodes,
            IList<ExprAggregateNodeGroupKey> groupKeyExpressions,
            bool hasGroupByClause,
            Attribute[] annotations,
            VariableCompileTimeResolver variableCompileTimeResolver,
            bool isDisallowNoReclaim,
            ExprNode whereClause,
            ExprNode havingClause,
            EventType[] typesPerStream,
            AggregationGroupByRollupDescForge groupByRollupDesc,
            string optionalContextName,
            IntoTableSpec intoTableSpec,
            TableCompileTimeResolver tableCompileTimeResolver,
            bool isUnidirectional,
            bool isFireAndForget,
            bool isOnSelect,
            ImportServiceCompileTime importService,
            StatementRawInfo raw,
            SerdeCompileTimeResolver serdeResolver)
        {
            // No aggregates used, we do not need this service
            if (selectAggregateExprNodes.IsEmpty() && havingAggregateExprNodes.IsEmpty()) {
                if (intoTableSpec != null) {
                    throw new ExprValidationException("Into-table requires at least one aggregation function");
                }

                return new AggregationServiceForgeDesc(
                    AggregationServiceNullFactory.INSTANCE,
                    EmptyList<AggregationServiceAggExpressionDesc>.Instance,
                    EmptyList<ExprAggregateNodeGroupKey>.Instance,
                    EmptyList<StmtClassForgeableFactory>.Instance);
            }

            // Validate the absence of "prev" function in where-clause:
            // Since the "previous" function does not post remove stream results, disallow when used with aggregations.
            if (whereClause != null || havingClause != null) {
                var visitor = new ExprNodePreviousVisitorWParent();
                whereClause?.Accept(visitor);

                havingClause?.Accept(visitor);

                if (visitor.Previous != null && !visitor.Previous.IsEmpty()) {
                    string funcname = visitor.Previous[0]
                        .Second.PreviousType.ToString()
                        .ToLowerInvariant();
                    throw new ExprValidationException(
                        "The '" +
                        funcname +
                        "' function may not occur in the where-clause or having-clause of a statement with aggregations as 'previous' does not provide remove stream data; Use the 'first','last','window' or 'count' aggregation functions instead");
                }
            }

            // Compile a map of aggregation nodes and equivalent-to aggregation nodes.
            // Equivalent-to functions are for example "select sum(a*b), 5*sum(a*b)".
            // Reducing the total number of aggregation functions.
            var aggregations = new List<AggregationServiceAggExpressionDesc>();
            var intoTableNonRollup = groupByRollupDesc == null && intoTableSpec != null;
            foreach (var selectAggNode in selectAggregateExprNodes) {
                AddEquivalent(selectAggNode, aggregations, intoTableNonRollup);
            }

            foreach (var havingAggNode in havingAggregateExprNodes) {
                AddEquivalent(havingAggNode, aggregations, intoTableNonRollup);
            }

            foreach (var orderByAggNode in orderByAggregateExprNodes) {
                AddEquivalent(orderByAggNode, aggregations, intoTableNonRollup);
            }

            // Construct a list of evaluation node for the aggregation functions (regular agg).
            // For example "sum(2 * 3)" would make the sum an evaluation node.
            IList<ExprForge[]> methodAggForgesList = new List<ExprForge[]>();
            foreach (var aggregation in aggregations) {
                var aggregateNode = aggregation.AggregationNode;
                if (!aggregateNode.Factory.IsAccessAggregation) {
                    var forges = aggregateNode.Factory.GetMethodAggregationForge(
                        typesPerStream.Length > 1,
                        typesPerStream);
                    methodAggForgesList.Add(forges);
                }
            }

            // determine local group-by, report when hook provided
            var localGroupDesc = AnalyzeLocalGroupBy(aggregations, groupByNodes, groupByRollupDesc, intoTableSpec);

            // determine binding
            if (intoTableSpec != null) {
                // obtain metadata
                var metadata = tableCompileTimeResolver.Resolve(intoTableSpec.Name);
                if (metadata == null) {
                    throw new ExprValidationException(
                        "Invalid into-table clause: Failed to find table by name '" + intoTableSpec.Name + "'");
                }

                EPLValidationUtil.ValidateContextName(
                    true,
                    intoTableSpec.Name,
                    metadata.OptionalContextName,
                    optionalContextName,
                    false);

                // validate group keys
                var groupByTypes = ExprNodeUtilityQuery.GetExprResultTypes(groupByNodes);
                var keyTypes = metadata.IsKeyed ? metadata.KeyTypes : new Type[0];
                ExprTableNodeUtil.ValidateExpressions(
                    intoTableSpec.Name,
                    groupByTypes,
                    "group-by",
                    groupByNodes,
                    keyTypes,
                    "group-by");

                // determine how this binds to existing aggregations, assign column numbers
                var bindingMatchResult = MatchBindingsAssignColumnNumbers(
                    intoTableSpec,
                    metadata,
                    aggregations,
                    selectClauseNamedNodes,
                    methodAggForgesList,
                    declaredExpressions,
                    importService,
                    raw.StatementName);

                // return factory
                AggregationServiceFactoryForge serviceForgeX = new AggregationServiceFactoryForgeTable(
                    metadata,
                    bindingMatchResult.MethodPairs,
                    bindingMatchResult.TargetStates,
                    bindingMatchResult.Agents,
                    groupByRollupDesc);
                return new AggregationServiceForgeDesc(serviceForgeX, aggregations, groupKeyExpressions, EmptyList<StmtClassForgeableFactory>.Instance);
            }

            // Assign a column number to each aggregation node. The regular aggregation goes first followed by access-aggregation.
            var columnNumber = 0;
            foreach (var entry in aggregations) {
                if (!entry.Factory.IsAccessAggregation) {
                    entry.SetColumnNum(columnNumber++);
                }
            }

            foreach (var entry in aggregations) {
                if (entry.Factory.IsAccessAggregation) {
                    entry.SetColumnNum(columnNumber++);
                }
            }

            // determine method aggregation factories and evaluators(non-access)
            var methodAggForges = methodAggForgesList.ToArray();
            var methodAggFactories = new AggregationForgeFactory[methodAggForges.Length];
            var count = 0;
            foreach (var aggregation in aggregations) {
                var aggregateNode = aggregation.AggregationNode;
                if (!aggregateNode.Factory.IsAccessAggregation) {
                    methodAggFactories[count] = aggregateNode.Factory;
                    count++;
                }
            }

            // handle access aggregations
            var multiFunctionAggPlan = AggregationMultiFunctionAnalysisHelper.AnalyzeAccessAggregations(
                aggregations,
                importService,
                isFireAndForget,
                raw.StatementName,
                groupByNodes);
            var accessorPairsForge = multiFunctionAggPlan.AccessorPairsForge;
            var accessFactories = multiFunctionAggPlan.StateFactoryForges;
            var hasAccessAgg = accessorPairsForge.Length > 0;
            var hasMethodAgg = methodAggFactories.Length > 0;

            AggregationServiceFactoryForge serviceForge;
            var useFlags = new AggregationUseFlags(isUnidirectional, isFireAndForget, isOnSelect);
            var additionalForgeables = new List<StmtClassForgeableFactory>();

            // analyze local group by
            AggregationLocalGroupByPlanForge localGroupByPlan = null;
            if (localGroupDesc != null) {
                AggregationLocalGroupByPlanDesc plan = AggregationGroupByLocalGroupByAnalyzer.Analyze(
                    methodAggForges,
                    methodAggFactories,
                    accessFactories,
                    localGroupDesc,
                    groupByNodes,
                    groupByMultiKey,
                    accessorPairsForge,
                    raw,
                    serdeResolver);
                localGroupByPlan = plan.Forge;
                additionalForgeables.AddAll(plan.AdditionalForgeables);

                try {
                    var hook = (AggregationLocalLevelHook) ImportUtil.GetAnnotationHook(
                        annotations,
                        HookType.INTERNAL_AGGLOCALLEVEL,
                        typeof(AggregationLocalLevelHook),
                        importService);
                    hook?.Planned(localGroupDesc, localGroupByPlan);
                }
                catch (ExprValidationException) {
                    throw new EPException("Failed to obtain hook for " + HookType.INTERNAL_AGGLOCALLEVEL);
                }
            }

            // Handle without a group-by clause: we group all into the same pot
            var rowStateDesc = new AggregationRowStateForgeDesc(
                hasMethodAgg ? methodAggFactories : null,
                hasMethodAgg ? methodAggForges : null,
                hasAccessAgg ? accessFactories : null,
                hasAccessAgg ? accessorPairsForge : null,
                useFlags);
            if (!hasGroupByClause) {
                if (localGroupByPlan != null) {
                    serviceForge = new AggSvcLocalGroupByForge(false, localGroupByPlan, useFlags);
                }
                else {
                    serviceForge = new AggregationServiceGroupAllForge(rowStateDesc);
                }
            }
            else {
                var groupDesc = new AggGroupByDesc(
                    rowStateDesc,
                    isUnidirectional,
                    isFireAndForget,
                    isOnSelect,
                    groupByNodes,
                    groupByMultiKey);
                var hasNoReclaim = HintEnum.DISABLE_RECLAIM_GROUP.GetHint(annotations) != null;
                var reclaimGroupAged = HintEnum.RECLAIM_GROUP_AGED.GetHint(annotations);
                var reclaimGroupFrequency = HintEnum.RECLAIM_GROUP_AGED.GetHint(annotations);
                if (localGroupByPlan != null) {
                    serviceForge = new AggSvcLocalGroupByForge(true, localGroupByPlan, useFlags);
                }
                else {
                    if (!isDisallowNoReclaim && hasNoReclaim) {
                        if (groupByRollupDesc != null) {
                            throw GetRollupReclaimEx();
                        }

                        serviceForge = new AggregationServiceGroupByForge(groupDesc, importService.TimeAbacus);
                    }
                    else if (!isDisallowNoReclaim && reclaimGroupAged != null) {
                        if (groupByRollupDesc != null) {
                            throw GetRollupReclaimEx();
                        }

                        CompileReclaim(
                            groupDesc,
                            reclaimGroupAged,
                            reclaimGroupFrequency,
                            variableCompileTimeResolver,
                            optionalContextName);
                        serviceForge = new AggregationServiceGroupByForge(groupDesc, importService.TimeAbacus);
                    }
                    else if (groupByRollupDesc != null) {
                        serviceForge = new AggSvcGroupByRollupForge(rowStateDesc, groupByRollupDesc, groupByNodes);
                    }
                    else {
                        groupDesc.IsRefcounted = true;
                        serviceForge = new AggregationServiceGroupByForge(groupDesc, importService.TimeAbacus);
                    }
                }
            }

            return new AggregationServiceForgeDesc(serviceForge, aggregations, groupKeyExpressions, additionalForgeables);
        }