diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/DelegationValidationStrategy.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/DelegationValidationStrategy.cs index 651d818197..c0117671d0 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/DelegationValidationStrategy.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/DelegationValidationStrategy.cs @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +using System.Collections.Generic; using System.Globalization; using System.Security.Authentication.ExtendedProtection; using Microsoft.PowerFx.Core.Binding; using Microsoft.PowerFx.Core.Binding.BindInfo; using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.Errors; +using Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata; using Microsoft.PowerFx.Core.Localization; using Microsoft.PowerFx.Core.Logging.Trackers; using Microsoft.PowerFx.Core.Texl.Builtins; @@ -18,17 +20,17 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationStrategies { internal interface ICallNodeDelegatableNodeValidationStrategy { - bool IsValidCallNode(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, TexlFunction trackingFunction = null); + bool IsValidCallNode(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope, TexlFunction trackingFunction = null); } internal interface IDottedNameNodeDelegatableNodeValidationStrategy { - bool IsValidDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, IOpDelegationStrategy opDelStrategy); + bool IsValidDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, IOpDelegationStrategy opDelStrategy, bool nodeInheritsRowScope); } internal interface IFirstNameNodeDelegatableNodeValidationStrategy { - bool IsValidFirstNameNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy); + bool IsValidFirstNameNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy, bool nodeInheritsRowScope); } internal class DelegationValidationStrategy @@ -91,7 +93,7 @@ protected void SuggestDelegationHint(TexlNode node, TexlBinding binding) SuggestDelegationHint(node, binding, null); } - private bool IsValidRowScopedDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, out bool isRowScopedDelegationExempted) + private bool IsValidRowScopedDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope, out bool isRowScopedDelegationExempted) { Contracts.AssertValue(node); Contracts.AssertValue(binding); @@ -108,18 +110,18 @@ private bool IsValidRowScopedDottedNameNode(DottedNameNode node, TexlBinding bin if (node.Left.Kind == NodeKind.Call) { - return IsValidCallNode(node.Left as CallNode, binding, metadata); + return IsValidCallNode(node.Left as CallNode, binding, metadata, nodeInheritsRowScope); } if (node.Left.Kind == NodeKind.DottedName) { - return IsValidRowScopedDottedNameNode(node.Left.AsDottedName(), binding, metadata, out isRowScopedDelegationExempted); + return IsValidRowScopedDottedNameNode(node.Left.AsDottedName(), binding, metadata, nodeInheritsRowScope, out isRowScopedDelegationExempted); } return node.Left.Kind == NodeKind.FirstName; } - private bool IsValidNonRowScopedDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, IOpDelegationStrategy opDelStrategy) + private bool IsValidNonRowScopedDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, IOpDelegationStrategy opDelStrategy, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(binding); @@ -127,12 +129,12 @@ private bool IsValidNonRowScopedDottedNameNode(DottedNameNode node, TexlBinding if (node.Left.Kind == NodeKind.Call) { - return IsValidCallNode(node.Left as CallNode, binding, metadata); + return IsValidCallNode(node.Left as CallNode, binding, metadata, nodeInheritsRowScope); } if (node.Left.Kind == NodeKind.DottedName) { - return IsValidDottedNameNode(node.Left.AsDottedName(), binding, metadata, opDelStrategy); + return IsValidDottedNameNode(node.Left.AsDottedName(), binding, metadata, opDelStrategy, nodeInheritsRowScope); } if (node.Left.Kind == NodeKind.FirstName) @@ -175,19 +177,19 @@ private OperationCapabilityMetadata GetScopedOperationCapabilityMetadata(IDelega return delegationMetadata.FilterDelegationMetadata; } - public bool IsValidDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, IOpDelegationStrategy opDelStrategy) + public bool IsValidDottedNameNode(DottedNameNode node, TexlBinding binding, OperationCapabilityMetadata metadata, IOpDelegationStrategy opDelStrategy, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(binding); Contracts.AssertValueOrNull(opDelStrategy); - var isRowScoped = binding.IsRowScope(node); + var isRowScoped = binding.IsRowScope(node) || nodeInheritsRowScope; if (!isRowScoped) { - return IsValidNonRowScopedDottedNameNode(node, binding, metadata, opDelStrategy); + return IsValidNonRowScopedDottedNameNode(node, binding, metadata, opDelStrategy, nodeInheritsRowScope); } - if (!IsValidRowScopedDottedNameNode(node, binding, metadata, out var isRowScopedDelegationExempted)) + if (!IsValidRowScopedDottedNameNode(node, binding, metadata, nodeInheritsRowScope, out var isRowScopedDelegationExempted)) { var telemetryMessage = string.Format(CultureInfo.InvariantCulture, "Kind:{0}, isRowScoped:{1}", node.Kind, isRowScoped); @@ -262,13 +264,13 @@ public bool IsValidDottedNameNode(DottedNameNode node, TexlBinding binding, Oper return opDelStrategy?.IsOpSupportedByColumn(entityCapabilityMetadata, node, entityColumnPath, binding) ?? true; } - public bool IsValidFirstNameNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy) + public bool IsValidFirstNameNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(binding); Contracts.AssertValueOrNull(opDelStrategy); - var isRowScoped = binding.IsRowScope(node); + var isRowScoped = binding.IsRowScope(node) || nodeInheritsRowScope; var isValid = IsValidAsyncOrImpureNode(node, binding); if (isValid && !isRowScoped) { @@ -281,7 +283,7 @@ public bool IsValidFirstNameNode(FirstNameNode node, TexlBinding binding, IOpDel return false; } - return IsDelegatableColumnNode(node, binding, opDelStrategy, Function.FunctionDelegationCapability); + return IsDelegatableColumnNode(node, binding, opDelStrategy, Function.FunctionDelegationCapability, nodeInheritsRowScope); } private IDelegationMetadata GetCapabilityMetadata(FirstNameInfo info) @@ -312,12 +314,12 @@ private IDelegationMetadata GetCapabilityMetadata(FirstNameInfo info) } // Verifies if provided column node supports delegation. - protected bool IsDelegatableColumnNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy, DelegationCapability capability) + protected bool IsDelegatableColumnNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy, DelegationCapability capability, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(binding); Contracts.AssertValueOrNull(opDelStrategy); - Contracts.Assert(binding.IsRowScope(node)); + Contracts.Assert(binding.IsRowScope(node) || nodeInheritsRowScope); var firstNameInfo = binding.GetInfo(node.AsFirstName()); if (firstNameInfo == null) @@ -359,7 +361,7 @@ protected bool IsDelegatableColumnNode(FirstNameNode node, TexlBinding binding, return true; } - public virtual bool IsValidCallNode(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, TexlFunction trackingFunction = null) + public virtual bool IsValidCallNode(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope, TexlFunction trackingFunction = null) { // Functions may have their specific CallNodeDelegationStrategies (i.e. AsType, User) // so, if available, we need to ensure we use their specific delegation strategy. @@ -370,13 +372,13 @@ public virtual bool IsValidCallNode(CallNode node, TexlBinding binding, Operatio if (function != null && function != Function) { // We need to keep track of the tracking function for delegation tracking telemetry to be consistent. - return function.GetCallNodeDelegationStrategy().IsValidCallNode(node, binding, metadata, trackingFunction ?? Function); + return function.GetCallNodeDelegationStrategy().IsValidCallNode(node, binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope, trackingFunction ?? Function); } - return IsValidCallNodeInternal(node, binding, metadata, trackingFunction ?? Function); + return IsValidCallNodeInternal(node, binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope, trackingFunction ?? Function); } - protected bool IsValidCallNodeInternal(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, TexlFunction trackingFunction = null) + protected bool IsValidCallNodeInternal(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope, TexlFunction trackingFunction = null) { Contracts.AssertValue(node); Contracts.AssertValue(binding); @@ -393,7 +395,7 @@ protected bool IsValidCallNodeInternal(CallNode node, TexlBinding binding, Opera } // If the node is not row scoped and it's valid then it can be delegated. - var isRowScoped = binding.IsRowScope(node); + var isRowScoped = binding.IsRowScope(node) || nodeInheritsRowScope; if (!isRowScoped) { return true; @@ -405,7 +407,17 @@ protected bool IsValidCallNodeInternal(CallNode node, TexlBinding binding, Opera SuggestDelegationHint(node, binding, warning, new object[] { callInfo?.Function.Name }); } - if (callInfo?.Function != null && ((TexlFunction)callInfo.Function).IsRowScopedServerDelegatable(node, binding, metadata)) + if (callInfo?.Function is UserDefinedFunction udf && + node.Parent?.Parent is CallNode parentNode && binding.GetInfo(parentNode).Function is FilterFunctionBase filterFunc) + { + if (filterFunc.IsValidDelegatableFilterPredicateNode(udf.Binding.Top, udf.Binding, metadata as FilterOpMetadata, generateHints: false, nodeInheritsRowScope: isRowScoped)) + { + udf.SetSupportRowScopedServerDelegation(true); + return true; + } + } + + if (callInfo?.Function != null && ((TexlFunction)callInfo.Function).IsRowScopedServerDelegatable(node, binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope)) { return true; } diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/IOpDelegationStrategy.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/IOpDelegationStrategy.cs index 41bfc2a4d3..514b511fc8 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/IOpDelegationStrategy.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/IOpDelegationStrategy.cs @@ -13,6 +13,6 @@ internal interface IOpDelegationStrategy bool IsOpSupportedByTable(OperationCapabilityMetadata metadata, TexlNode node, TexlBinding binder); - bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding); + bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, bool nodeInheritsRowScope = false); } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/InOpDelegationStrategy.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/InOpDelegationStrategy.cs index 1152512a09..f41a6fd7ec 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/InOpDelegationStrategy.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/InOpDelegationStrategy.cs @@ -25,7 +25,7 @@ public InOpDelegationStrategy(BinaryOpNode node, TexlFunction function) _binaryOpNode = node; } - public override bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding) + public override bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(metadata); @@ -40,7 +40,7 @@ public override bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadat var isRHSDelegableTable = IsRHSDelegableTable(binding, binaryOpNode, metadata); if (isRHSDelegableTable && binaryOpNode.Left is DottedNameNode dottedField && binding.GetType(dottedField.Left).HasExpandInfo) { - return base.IsSupportedOpNode(node, metadata, binding); + return base.IsSupportedOpNode(node, metadata, binding, nodeInheritsRowScope); } DName columnName = default; @@ -52,7 +52,7 @@ public override bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadat return false; } - var isRowScopedOrLambda = IsRowScopedOrLambda(binding, node, info, columnName, metadata); + var isRowScopedOrLambda = IsRowScopedOrLambda(binding, node, info, columnName, metadata) || nodeInheritsRowScope; if (!isRowScopedOrLambda) { return false; @@ -68,7 +68,7 @@ public override bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadat return false; } - return base.IsSupportedOpNode(node, metadata, binding); + return base.IsSupportedOpNode(node, metadata, binding, nodeInheritsRowScope); } public bool IsRHSDelegableTable(TexlBinding binding, BinaryOpNode binaryOpNode, OperationCapabilityMetadata metadata) diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/OpDelegationStrategy.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/OpDelegationStrategy.cs index 2f5098d18a..f8f8767ba5 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/OpDelegationStrategy.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/OpDelegationStrategy.cs @@ -80,7 +80,7 @@ public virtual bool IsOpSupportedByTable(OperationCapabilityMetadata metadata, T } // Verifies if given kind of node is supported by function delegation. - private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, IOpDelegationStrategy opDelStrategy, bool isRHSNode) + private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, IOpDelegationStrategy opDelStrategy, bool isRHSNode, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(metadata); @@ -112,7 +112,7 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata } // Non row-scope, non async, pure nodes should always be valid because we can calculate value in runtime before delegation. - if (!binding.IsRowScope(node) && !binding.IsAsync(node) && binding.IsPure(node)) + if (!binding.IsRowScope(node) && !binding.IsAsync(node) && binding.IsPure(node) && !nodeInheritsRowScope) { return true; } @@ -127,7 +127,7 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata } var dottedNodeValStrategy = _function.GetDottedNameNodeDelegationStrategy(); - return dottedNodeValStrategy.IsValidDottedNameNode(node.AsDottedName(), binding, metadata, opDelStrategy); + return dottedNodeValStrategy.IsValidDottedNameNode(node.AsDottedName(), binding, metadata, opDelStrategy, nodeInheritsRowScope); } case NodeKind.Call: @@ -138,13 +138,13 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata } var cNodeValStrategy = _function.GetCallNodeDelegationStrategy(); - return cNodeValStrategy.IsValidCallNode(node.AsCall(), binding, metadata); + return cNodeValStrategy.IsValidCallNode(node.AsCall(), binding, metadata, nodeInheritsRowScope); } case NodeKind.FirstName: { var firstNameNodeValStrategy = _function.GetFirstNameNodeDelegationStrategy(); - return firstNameNodeValStrategy.IsValidFirstNameNode(node.AsFirstName(), binding, opDelStrategy); + return firstNameNodeValStrategy.IsValidFirstNameNode(node.AsFirstName(), binding, opDelStrategy, nodeInheritsRowScope); } case NodeKind.UnaryOp: @@ -156,7 +156,7 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata var unaryopNode = node.AsUnaryOpLit(); var unaryOpNodeDelegationStrategy = _function.GetOpDelegationStrategy(unaryopNode.Op); - return unaryOpNodeDelegationStrategy.IsSupportedOpNode(unaryopNode, metadata, binding); + return unaryOpNodeDelegationStrategy.IsSupportedOpNode(unaryopNode, metadata, binding, nodeInheritsRowScope); } case NodeKind.BinaryOp: @@ -172,7 +172,7 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata var binaryOpDelStrategy = (opDelStrategy as BinaryOpDelegationStrategy).VerifyValue(); Contracts.Assert(binaryOpNode.Op == binaryOpDelStrategy.Op); - if (!opDelStrategy.IsSupportedOpNode(node, metadata, binding)) + if (!opDelStrategy.IsSupportedOpNode(node, metadata, binding, nodeInheritsRowScope: nodeInheritsRowScope)) { SuggestDelegationHint(binaryOpNode, binding); return false; @@ -207,7 +207,7 @@ private bool IsColumnNode(TexlNode node, TexlBinding binding) return (node.Kind == NodeKind.FirstName) && binding.IsRowScope(node); } - private bool DoCoercionCheck(BinaryOpNode binaryOpNode, OperationCapabilityMetadata metadata, TexlBinding binding) + private bool DoCoercionCheck(BinaryOpNode binaryOpNode, OperationCapabilityMetadata metadata, TexlBinding binding, bool nodeInheritsRowScope) { Contracts.AssertValue(binaryOpNode); Contracts.AssertValue(metadata); @@ -224,7 +224,7 @@ private bool DoCoercionCheck(BinaryOpNode binaryOpNode, OperationCapabilityMetad // If rhs is a column of type DateTime and lhs is row scoped then we will need to apply the coercion on rhs. So check if coercion function date is supported or not. if (IsColumnNode(binaryOpNode.Right, binding) && binding.IsRowScope(binaryOpNode.Left)) { - return IsDelegatableColumnNode(binaryOpNode.Right.AsFirstName(), binding, null, DelegationCapability.Date); + return IsDelegatableColumnNode(binaryOpNode.Right.AsFirstName(), binding, null, DelegationCapability.Date, nodeInheritsRowScope); } // If lhs is rowscoped but not a field reference and rhs is rowscoped then we need to check if it's supported at table level. @@ -243,7 +243,7 @@ private bool DoCoercionCheck(BinaryOpNode binaryOpNode, OperationCapabilityMetad // If lhs is a column of type DateTime and RHS is also row scoped then check if coercion function date is supported or not. if (IsColumnNode(binaryOpNode.Left, binding) && binding.IsRowScope(binaryOpNode.Right)) { - return IsDelegatableColumnNode(binaryOpNode.Left.AsFirstName(), binding, null, DelegationCapability.Date); + return IsDelegatableColumnNode(binaryOpNode.Left.AsFirstName(), binding, null, DelegationCapability.Date, nodeInheritsRowScope); } // If lhs is rowscoped but not a field reference and rhs is rowscoped then we need to check if it's supported at table level. @@ -263,7 +263,7 @@ private bool DoCoercionCheck(BinaryOpNode binaryOpNode, OperationCapabilityMetad return true; } - public virtual bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding) + public virtual bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(metadata); @@ -298,13 +298,13 @@ public virtual bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata return false; } - if (!IsSupportedNode(binaryOpNode.Left, metadata, binding, opDelStrategy, false)) + if (!IsSupportedNode(binaryOpNode.Left, metadata, binding, opDelStrategy, false, nodeInheritsRowScope)) { SuggestDelegationHint(node, binding); return false; } - if (!IsSupportedNode(binaryOpNode.Right, metadata, binding, opDelStrategy, true)) + if (!IsSupportedNode(binaryOpNode.Right, metadata, binding, opDelStrategy, true, nodeInheritsRowScope)) { SuggestDelegationHint(node, binding); return false; @@ -322,7 +322,7 @@ public virtual bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata return true; } - if (!DoCoercionCheck(binaryOpNode, metadata, binding)) + if (!DoCoercionCheck(binaryOpNode, metadata, binding, nodeInheritsRowScope)) { SuggestDelegationHint(node, binding); return false; diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/UnaryOpDelegationStrategy.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/UnaryOpDelegationStrategy.cs index 38a04a711d..5880d52f1f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/UnaryOpDelegationStrategy.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationStrategies/UnaryOpDelegationStrategy.cs @@ -69,14 +69,14 @@ public virtual bool IsOpSupportedByTable(OperationCapabilityMetadata metadata, T return true; } - private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, IOpDelegationStrategy opDelStrategy) + private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, IOpDelegationStrategy opDelStrategy, bool nodeInheritsRowScope) { Contracts.AssertValue(node); Contracts.AssertValue(metadata); Contracts.AssertValue(binding); Contracts.AssertValue(opDelStrategy); - if (!binding.IsRowScope(node)) + if (!binding.IsRowScope(node) && nodeInheritsRowScope) { return true; } @@ -91,7 +91,7 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata } var dottedNodeValStrategy = _function.GetDottedNameNodeDelegationStrategy(); - return dottedNodeValStrategy.IsValidDottedNameNode(node.AsDottedName(), binding, metadata, opDelStrategy); + return dottedNodeValStrategy.IsValidDottedNameNode(node.AsDottedName(), binding, metadata, opDelStrategy, nodeInheritsRowScope); } case NodeKind.Call: @@ -102,13 +102,13 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata } var cNodeValStrategy = _function.GetCallNodeDelegationStrategy(); - return cNodeValStrategy.IsValidCallNode(node.AsCall(), binding, metadata); + return cNodeValStrategy.IsValidCallNode(node.AsCall(), binding, metadata, nodeInheritsRowScope); } case NodeKind.FirstName: { var firstNameNodeValStrategy = _function.GetFirstNameNodeDelegationStrategy(); - return firstNameNodeValStrategy.IsValidFirstNameNode(node.AsFirstName(), binding, opDelStrategy); + return firstNameNodeValStrategy.IsValidFirstNameNode(node.AsFirstName(), binding, opDelStrategy, nodeInheritsRowScope); } case NodeKind.UnaryOp: @@ -150,7 +150,7 @@ private bool IsSupportedNode(TexlNode node, OperationCapabilityMetadata metadata return false; } - public virtual bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding) + public virtual bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata metadata, TexlBinding binding, bool nodeInheritsRowScope = false) { Contracts.AssertValue(node); Contracts.AssertValue(metadata); @@ -179,7 +179,7 @@ public virtual bool IsSupportedOpNode(TexlNode node, OperationCapabilityMetadata return false; } - return IsSupportedNode(unaryOpNode.Child, metadata, binding, opDelStrategy); + return IsSupportedNode(unaryOpNode.Child, metadata, binding, opDelStrategy, nodeInheritsRowScope); } } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs index 5271868816..0e73690b14 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs @@ -945,12 +945,12 @@ public virtual bool SupportsPaging(CallNode callNode, TexlBinding binding) // Returns true if function is row scoped and supports delegation. // Needs to be overriden by functions (For example, IsBlank) which are not server delegatable themselves but can become one when scoped inside a delegatable function. - public virtual bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public virtual bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); - if (!binding.IsRowScope(callNode)) + if (!binding.IsRowScope(callNode) && !nodeInheritsRowScope) { return false; } diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs index f86086b4e6..4a5f65c02f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs @@ -12,6 +12,7 @@ using Microsoft.PowerFx.Core.Binding.BindInfo; using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.Errors; +using Microsoft.PowerFx.Core.Functions.Delegation; using Microsoft.PowerFx.Core.Functions.FunctionArgValidators; using Microsoft.PowerFx.Core.Glue; using Microsoft.PowerFx.Core.IR; @@ -44,7 +45,9 @@ internal class UserDefinedFunction : TexlFunction, IExternalPageableSymbol, IExt public bool IsPageable => _binding.IsPageable(_binding.Top); - public bool IsDelegatable => _binding.IsDelegatable(_binding.Top); + public bool IsDelegatable => _binding.IsDelegatable(_binding.Top); + + private bool _supportsRowScopedServerDelegation; public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) { @@ -53,7 +56,12 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) Contracts.Assert(binding.GetInfo(callNode).Function is UserDefinedFunction udf && udf.Binding != null); return base.IsServerDelegatable(callNode, binding) || IsDelegatable; - } + } + + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) + { + return _supportsRowScopedServerDelegation; + } public override bool SupportsParamCoercion => true; @@ -92,7 +100,8 @@ public UserDefinedFunction(string functionName, DType returnType, TexlNode body, this._args = args; this._isImperative = isImperative; - this.UdfBody = body; + this.UdfBody = body; + this._supportsRowScopedServerDelegation = false; } /// @@ -370,8 +379,13 @@ internal static bool IsRestrictedType(FormulaType ft) } return false; - } - + } + + internal void SetSupportRowScopedServerDelegation(bool value) + { + _supportsRowScopedServerDelegation = value; + } + /// /// NameResolver that combines global named resolver and params for user defined function. /// diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/AsType.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/AsType.cs index 38d9fc218b..ada070dacc 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/AsType.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/AsType.cs @@ -106,7 +106,7 @@ private static bool IsExternalSource(object externalDataSource) tDsInfo is IExternalTabularDataSource; } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { return metadata.IsDelegationSupportedByTable(DelegationCapability.AsType); } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountIf.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountIf.cs index f9168fe3cb..a84a7a358f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountIf.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountIf.cs @@ -130,7 +130,7 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) // Validate for each predicate node. for (var i = 1; i < args.Count; i++) { - if (!IsValidDelegatableFilterPredicateNode(args[i], binding, metadata)) + if (!IsValidDelegatableFilterPredicateNode(args[i], binding, metadata, false)) { SuggestDelegationHint(callNode, binding); return false; diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/DateTime.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/DateTime.cs index 56243875a4..d33556226a 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/DateTime.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/DateTime.cs @@ -51,15 +51,6 @@ internal abstract class ExtractDateTimeFunctionBase : BuiltinFunction public ExtractDateTimeFunctionBase(string name, TexlStrings.StringGetter description) : base(name, description, FunctionCategories.DateTime, DType.Number, 0, 1, 1, DType.DateTime) { - } - - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) - { - Contracts.AssertValue(callNode); - Contracts.AssertValue(binding); - Contracts.AssertValue(metadata); - - return base.IsRowScopedServerDelegatable(callNode, binding, metadata); } public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary nodeToCoercedTypeMap) diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/EndsWith.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/EndsWith.cs index acb665ab4b..4d6671f424 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/EndsWith.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/EndsWith.cs @@ -24,13 +24,13 @@ public EndsWithFunction() { } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); Contracts.AssertValue(metadata); - return IsRowScopedServerDelegatableHelper(callNode, binding, metadata); + return IsRowScopedServerDelegatableHelper(callNode, binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope); } public override IEnumerable GetSignatures() diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Filter.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Filter.cs index 09fc51746e..797fb66540 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Filter.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Filter.cs @@ -176,7 +176,7 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) // Validate for each predicate node. for (var i = 1; i < args.Count; i++) { - if (!IsValidDelegatableFilterPredicateNode(args[i], binding, metadata)) + if (!IsValidDelegatableFilterPredicateNode(args[i], binding, metadata, false)) { return false; } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/FilterDelegationBase.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/FilterDelegationBase.cs index e78c7fcbd4..20a9e83140 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/FilterDelegationBase.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/FilterDelegationBase.cs @@ -67,7 +67,7 @@ public override bool TryGetDelegationMetadata(CallNode node, TexlBinding binding // Determine whether a node can be delegated as part of a filter predicate. // The enforceBoolean flag determines whether to enforce the return type of the node. If the node is part of a filter predicate directly, it must return a boolean type. // If the node is used in other places inside a filter, such as in a nested LookUp reduction formula, it can return any type. - protected bool IsValidDelegatableFilterPredicateNode(TexlNode dsNode, TexlBinding binding, FilterOpMetadata filterMetadata, bool generateHints = true, bool enforceBoolean = true) + internal bool IsValidDelegatableFilterPredicateNode(TexlNode dsNode, TexlBinding binding, FilterOpMetadata filterMetadata, bool nodeInheritsRowScope, bool generateHints = true, bool enforceBoolean = true) { Contracts.AssertValue(dsNode); Contracts.AssertValue(binding); @@ -98,7 +98,7 @@ protected bool IsValidDelegatableFilterPredicateNode(TexlNode dsNode, TexlBindin var binaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op, opNode); Contracts.AssertValue(opNode); - if (!binaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, filterMetadata, binding)) + if (!binaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, filterMetadata, binding, nodeInheritsRowScope)) { return false; } @@ -114,7 +114,7 @@ protected bool IsValidDelegatableFilterPredicateNode(TexlNode dsNode, TexlBindin return false; } - if (!firstNameStrategy.IsValidFirstNameNode(dsNode.AsFirstName(), binding, null)) + if (!firstNameStrategy.IsValidFirstNameNode(dsNode.AsFirstName(), binding, null, nodeInheritsRowScope)) { return false; } @@ -130,7 +130,7 @@ protected bool IsValidDelegatableFilterPredicateNode(TexlNode dsNode, TexlBindin return false; } - if (!dottedNameStrategy.IsValidDottedNameNode(dsNode.AsDottedName(), binding, filterMetadata, null)) + if (!dottedNameStrategy.IsValidDottedNameNode(dsNode.AsDottedName(), binding, filterMetadata, null, nodeInheritsRowScope)) { return false; } @@ -144,7 +144,7 @@ protected bool IsValidDelegatableFilterPredicateNode(TexlNode dsNode, TexlBindin var unaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op); Contracts.AssertValue(opNode); - if (!unaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, filterMetadata, binding)) + if (!unaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, filterMetadata, binding, nodeInheritsRowScope)) { SuggestDelegationHint(dsNode, binding); return false; @@ -155,7 +155,7 @@ protected bool IsValidDelegatableFilterPredicateNode(TexlNode dsNode, TexlBindin case NodeKind.Call: { - if (!cNodeStrategy.IsValidCallNode(dsNode.AsCall(), binding, filterMetadata)) + if (!cNodeStrategy.IsValidCallNode(dsNode.AsCall(), binding, filterMetadata, nodeInheritsRowScope: nodeInheritsRowScope)) { return false; } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsBlank.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsBlank.cs index 8b9e8842cc..b086a20704 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsBlank.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsBlank.cs @@ -92,7 +92,7 @@ public IsBlankFunction() yield return new[] { TexlStrings.IsBlankArg1 }; } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); @@ -113,7 +113,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding if (binding.IsFullRecordRowScopeAccess(args[0])) { - return GetDottedNameNodeDelegationStrategy().IsValidDottedNameNode(args[0] as DottedNameNode, binding, metadata, opStrategy); + return GetDottedNameNodeDelegationStrategy().IsValidDottedNameNode(args[0] as DottedNameNode, binding, metadata, opStrategy, nodeInheritsRowScope: nodeInheritsRowScope); } if (args[0] is not FirstNameNode node) @@ -129,7 +129,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding } var firstNameNodeValidationStrategy = GetFirstNameNodeDelegationStrategy(); - return firstNameNodeValidationStrategy.IsValidFirstNameNode(node, binding, opStrategy); + return firstNameNodeValidationStrategy.IsValidFirstNameNode(node, binding, opStrategy, nodeInheritsRowScope: nodeInheritsRowScope); } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Len.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Len.cs index 11a3b6935d..921f7300ea 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Len.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Len.cs @@ -29,12 +29,7 @@ public LenFunction() yield return new[] { TexlStrings.LenArg1 }; } - public override DelegationCapability FunctionDelegationCapability => DelegationCapability.Length; - - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) - { - return base.IsRowScopedServerDelegatable(callNode, binding, metadata); - } + public override DelegationCapability FunctionDelegationCapability => DelegationCapability.Length; public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary nodeToCoercedTypeMap) { diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Logical.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Logical.cs index 7e15af17eb..1f682a6c81 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Logical.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Logical.cs @@ -85,7 +85,7 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp return fArgsValid; } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); @@ -115,7 +115,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding case NodeKind.FirstName: { var firstNameStrategy = GetFirstNameNodeDelegationStrategy(); - if (!firstNameStrategy.IsValidFirstNameNode(arg.AsFirstName(), binding, null)) + if (!firstNameStrategy.IsValidFirstNameNode(arg.AsFirstName(), binding, null, nodeInheritsRowScope: nodeInheritsRowScope)) { return false; } @@ -126,7 +126,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding case NodeKind.Call: { var cNodeStrategy = GetCallNodeDelegationStrategy(); - if (!cNodeStrategy.IsValidCallNode(arg.AsCall(), binding, metadata)) + if (!cNodeStrategy.IsValidCallNode(arg.AsCall(), binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope)) { SuggestDelegationHint(arg, binding); return false; @@ -138,7 +138,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding case NodeKind.DottedName: { var dottedStrategy = GetDottedNameNodeDelegationStrategy(); - if (!dottedStrategy.IsValidDottedNameNode(arg.AsDottedName(), binding, metadata, null)) + if (!dottedStrategy.IsValidDottedNameNode(arg.AsDottedName(), binding, metadata, null, nodeInheritsRowScope: nodeInheritsRowScope)) { SuggestDelegationHint(arg, binding); return false; @@ -151,7 +151,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding { var opNode = arg.AsBinaryOp(); var binaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op, opNode); - if (!binaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding)) + if (!binaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding, nodeInheritsRowScope: nodeInheritsRowScope)) { SuggestDelegationHint(arg, binding); return false; @@ -164,7 +164,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding { var opNode = arg.AsUnaryOpLit(); var unaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op); - if (!unaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding)) + if (!unaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding, nodeInheritsRowScope: nodeInheritsRowScope)) { SuggestDelegationHint(arg, binding); return false; diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Lookup.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Lookup.cs index 2440d591c9..d628df3db1 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Lookup.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Lookup.cs @@ -102,7 +102,7 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) return false; } - return IsValidDelegatableFilterPredicateNode(args[1], binding, metadata); + return IsValidDelegatableFilterPredicateNode(args[1], binding, metadata, false); } private bool TryGetFilterOpDelegationMetadata(CallNode callNode, TexlBinding binding, out FilterOpMetadata metadata) @@ -142,15 +142,16 @@ public override ICallNodeDelegatableNodeValidationStrategy GetCallNodeDelegation return new LookUpCallNodeDelegationStrategy(this); } - public bool IsValidDelegatableReductionNode(CallNode callNode, TexlNode reductionNode, TexlBinding binding) + public bool IsValidDelegatableReductionNode(CallNode callNode, TexlNode reductionNode, TexlBinding binding, bool nodeInheritsRowScope = false, TexlBinding udfBinding = null) { if (!TryGetFilterOpDelegationMetadata(callNode, binding, out var metadata)) { return false; } - // use a variation of the filter predicate logic to determine if the reduction formula is delegatable, without enforcing the return type must be boolean - return IsValidDelegatableFilterPredicateNode(reductionNode, binding, metadata, generateHints: false, enforceBoolean: false); + // use a variation of the filter predicate logic to determine if the reduction formula is delegatable, without enforcing the return type must be boolean + // if reduction node is a user defined function call node, we will need to provide the udf binding instead + return IsValidDelegatableFilterPredicateNode(reductionNode, udfBinding ?? binding, metadata, nodeInheritsRowScope, generateHints: false, enforceBoolean: false); } } @@ -161,7 +162,7 @@ public LookUpCallNodeDelegationStrategy(TexlFunction function) { } - public override bool IsValidCallNode(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, TexlFunction trackingFunction = null) + public override bool IsValidCallNode(CallNode node, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope, TexlFunction trackingFunction = null) { var function = binding.GetInfo(node)?.Function; var args = node.Args.Children.VerifyValue(); @@ -175,7 +176,7 @@ function is LookUpFunction lookup && args.Count > 2 && return false; } - return base.IsValidCallNode(node, binding, metadata, trackingFunction ?? Function); + return base.IsValidCallNode(node, binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope, trackingFunction ?? Function); } } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/LowerUpper.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/LowerUpper.cs index 41da68a9ee..ee0435c77f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/LowerUpper.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/LowerUpper.cs @@ -25,7 +25,7 @@ public LowerUpperFunction(bool isLower) public override DelegationCapability FunctionDelegationCapability => _isLower ? DelegationCapability.Lower : DelegationCapability.Upper; - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/NotFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/NotFunction.cs index cce6287576..f551984d57 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/NotFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/NotFunction.cs @@ -48,7 +48,7 @@ public override IOpDelegationStrategy GetOpDelegationStrategy(BinaryOp op, Power return new DefaultBinaryOpDelegationStrategy(op, BuiltinFunctionsCore.Filter); } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); @@ -72,7 +72,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding switch (argKind) { case NodeKind.FirstName: - return firstNameStrategy.IsValidFirstNameNode(args[0].AsFirstName(), binding, opStrategy); + return firstNameStrategy.IsValidFirstNameNode(args[0].AsFirstName(), binding, opStrategy, nodeInheritsRowScope: nodeInheritsRowScope); case NodeKind.Call: { @@ -81,7 +81,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding return false; } - return cNodeStrategy.IsValidCallNode(args[0].AsCall(), binding, metadata); + return cNodeStrategy.IsValidCallNode(args[0].AsCall(), binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope); } case NodeKind.BinaryOp: @@ -93,7 +93,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding var opNode = args[0].AsBinaryOp(); var binaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op, opNode); - return binaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding); + return binaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding, nodeInheritsRowScope: nodeInheritsRowScope); } case NodeKind.UnaryOp: @@ -105,11 +105,11 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding var opNode = args[0].AsUnaryOpLit(); var unaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op); - return unaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding); + return unaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding, nodeInheritsRowScope: nodeInheritsRowScope); } case NodeKind.DottedName: - return dottedStrategy.IsValidDottedNameNode(args[0].AsDottedName(), binding, metadata, opStrategy); + return dottedStrategy.IsValidDottedNameNode(args[0].AsDottedName(), binding, metadata, opStrategy, nodeInheritsRowScope: nodeInheritsRowScope); default: return argKind == NodeKind.BoolLit; diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Proper.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Proper.cs index 8d49ff63e6..85901383ce 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Proper.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Proper.cs @@ -19,7 +19,7 @@ public ProperFunction() { } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { return false; } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StartsWith.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StartsWith.cs index e3778ca453..19b4e9d5b0 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StartsWith.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StartsWith.cs @@ -21,13 +21,13 @@ public StartsWithFunction() { } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); Contracts.AssertValue(metadata); - return IsRowScopedServerDelegatableHelper(callNode, binding, metadata); + return IsRowScopedServerDelegatableHelper(callNode, binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope); } public override IEnumerable GetSignatures() diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StatisticalTableFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StatisticalTableFunction.cs index 98a6397e78..59af39677a 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StatisticalTableFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StatisticalTableFunction.cs @@ -89,11 +89,11 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) if (binding.IsFullRecordRowScopeAccess(args[1])) { - return GetDottedNameNodeDelegationStrategy().IsValidDottedNameNode(args[1].AsDottedName(), binding, null, null); + return GetDottedNameNodeDelegationStrategy().IsValidDottedNameNode(args[1].AsDottedName(), binding, null, null, false); } var firstNameStrategy = GetFirstNameNodeDelegationStrategy().VerifyValue(); - return firstNameStrategy.IsValidFirstNameNode(args[1].AsFirstName(), binding, null); + return firstNameStrategy.IsValidFirstNameNode(args[1].AsFirstName(), binding, null, false); } public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary nodeToCoercedTypeMap) diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringOneArgFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringOneArgFunction.cs index 9a225e997d..9439d54a5d 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringOneArgFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringOneArgFunction.cs @@ -38,7 +38,7 @@ public StringOneArgFunction(string name, TexlStrings.StringGetter description, F yield return new[] { TexlStrings.StringFuncArg1 }; } - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); @@ -60,7 +60,7 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding case NodeKind.FirstName: { var firstNameStrategy = GetFirstNameNodeDelegationStrategy(); - return firstNameStrategy.IsValidFirstNameNode(args[0].AsFirstName(), binding, null); + return firstNameStrategy.IsValidFirstNameNode(args[0].AsFirstName(), binding, null, nodeInheritsRowScope: nodeInheritsRowScope); } case NodeKind.Call: @@ -71,13 +71,13 @@ public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding } var cNodeStrategy = GetCallNodeDelegationStrategy(); - return cNodeStrategy.IsValidCallNode(args[0].AsCall(), binding, metadata); + return cNodeStrategy.IsValidCallNode(args[0].AsCall(), binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope); } case NodeKind.DottedName: { var dottedStrategy = GetDottedNameNodeDelegationStrategy(); - return dottedStrategy.IsValidDottedNameNode(args[0].AsDottedName(), binding, metadata, null); + return dottedStrategy.IsValidDottedNameNode(args[0].AsDottedName(), binding, metadata, null, nodeInheritsRowScope: nodeInheritsRowScope); } default: diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringTwoArgFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringTwoArgFunction.cs index 39b9a1e974..d09c8f5206 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringTwoArgFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/StringTwoArgFunction.cs @@ -28,7 +28,7 @@ public StringTwoArgFunction(string name, TexlStrings.StringGetter description, D { } - protected bool IsRowScopedServerDelegatableHelper(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) + protected bool IsRowScopedServerDelegatableHelper(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata, bool nodeInheritsRowScope = false) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); @@ -57,7 +57,7 @@ protected bool IsRowScopedServerDelegatableHelper(CallNode callNode, TexlBinding { case NodeKind.FirstName: var firstNameStrategy = GetFirstNameNodeDelegationStrategy(); - if (!firstNameStrategy.IsValidFirstNameNode(arg.AsFirstName(), binding, null)) + if (!firstNameStrategy.IsValidFirstNameNode(arg.AsFirstName(), binding, null, nodeInheritsRowScope: nodeInheritsRowScope)) { return false; } @@ -70,7 +70,7 @@ protected bool IsRowScopedServerDelegatableHelper(CallNode callNode, TexlBinding } var cNodeStrategy = GetCallNodeDelegationStrategy(); - if (!cNodeStrategy.IsValidCallNode(arg.AsCall(), binding, metadata)) + if (!cNodeStrategy.IsValidCallNode(arg.AsCall(), binding, metadata, nodeInheritsRowScope: nodeInheritsRowScope)) { return false; } @@ -81,7 +81,7 @@ protected bool IsRowScopedServerDelegatableHelper(CallNode callNode, TexlBinding case NodeKind.DottedName: { var dottedStrategy = GetDottedNameNodeDelegationStrategy(); - return dottedStrategy.IsValidDottedNameNode(arg.AsDottedName(), binding, metadata, null); + return dottedStrategy.IsValidDottedNameNode(arg.AsDottedName(), binding, metadata, null, nodeInheritsRowScope: nodeInheritsRowScope); } default: diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trim.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trim.cs index af2ca00c88..46b40b885c 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trim.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trim.cs @@ -37,15 +37,6 @@ public TrimEndsFunction() } public override DelegationCapability FunctionDelegationCapability => DelegationCapability.Trim; - - public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) - { - Contracts.AssertValue(callNode); - Contracts.AssertValue(binding); - Contracts.AssertValue(metadata); - - return base.IsRowScopedServerDelegatable(callNode, binding, metadata); - } } // Trim(arg:*[s]) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs index 3baccab4f1..ff37c76800 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs @@ -294,7 +294,7 @@ public bool TryGetRelatedColumn(string selectColumnName, out string additionalCo } } - internal class TestDelegableDataSource : TestDataSource + internal class TestDelegableDataSource : TestDataSource, IExternalDelegatableSymbol { private readonly TabularDataQueryOptions _queryOptions; private readonly IDelegationMetadata _delegationMetadata; diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestUtils.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestUtils.cs index 3f30ef8ea8..70bf4e61ce 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestUtils.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestUtils.cs @@ -302,7 +302,7 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding) if (dataSource != null && dataSource.DelegationMetadata != null) { var metadata = dataSource.DelegationMetadata.FilterDelegationMetadata; - if (!IsValidDelegatableFilterPredicateNode(args[1], binding, metadata, false)) + if (!IsValidDelegatableFilterPredicateNode(args[1], binding, metadata, false, false)) { return false; } diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs index b71764da47..95153faaa7 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs @@ -835,6 +835,50 @@ public void TestInheritanceOfDelegationWarningsInUDFs() Assert.True(result.IsSuccess); func = engine.Functions.WithName("NonDelegatableUDF2").First() as UserDefinedFunction; Assert.True(func.HasDelegationWarning); + } + + [Theory] + [InlineData( + "DelegatableUDF(Value: Number):Boolean = Value > 5;", + "DelegatableUDF2():MyDataSourceTableType = Filter(MyDataSource, DelegatableUDF(Value));", + false)] + [InlineData( + "NonDelegatableUDF(Value: Number):Boolean = Sqrt(Value) > 5;", + "NonDelegatableUDF2():MyDataSourceTableType = Filter(MyDataSource, NonDelegatableUDF(Value));", + true)] + public void TestFilterFunctionDelegationUDF(string formula, string formula2, bool expectedError) + { + var symbolTable = new DelegatableSymbolTable(); + var schema = DType.CreateTable( + new TypedName(DType.Number, new DName("Value"))); + symbolTable.AddEntity(new TestDelegableDataSource( + "MyDataSource", + schema, + new TestDelegationMetadata( + new DelegationCapability(DelegationCapability.Filter), + schema, + new FilterOpMetadata( + schema, + new Dictionary(), + new Dictionary(), + new DelegationCapability(DelegationCapability.GreaterThan), + null)), + true)); + symbolTable.AddType(new DName("MyDataSourceTableType"), FormulaType.Build(schema)); + var config = new PowerFxConfig() + { + SymbolTable = symbolTable + }; + var engine = new RecalcEngine(config); + + var result = engine.AddUserDefinedFunction(formula, CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: true); + Assert.True(result.IsSuccess); + var func = engine.Functions.WithName(expectedError ? "NonDelegatableUDF" : "DelegatableUDF").First() as UserDefinedFunction; + + result = engine.AddUserDefinedFunction(formula2, CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: true); + Assert.True(result.IsSuccess); + func = engine.Functions.WithName(expectedError ? "NonDelegatableUDF2" : "DelegatableUDF2").First() as UserDefinedFunction; + Assert.True(func.HasDelegationWarning == expectedError); } // Binding to inner functions does not impact outer functions.