/// <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)); }
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); }