Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IsDelegatable and IsPageable flags to UDF class #2459

Merged
merged 9 commits into from
Aug 16, 2024
6 changes: 3 additions & 3 deletions src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ private bool SupportsServerDelegation(CallNode node)
return false;
}

var isServerDelegatable = function.IsServerDelegatable(node, this);
var isServerDelegatable = (function is not UserDefinedFunction udf || udf.Binding != null) && function.IsServerDelegatable(node, this);
LogTelemetryForFunction(function, node, this, isServerDelegatable);
return isServerDelegatable;
}
Expand Down Expand Up @@ -1194,7 +1194,7 @@ public void CheckAndMarkAsPageable(CallNode node, TexlFunction func)
Contracts.AssertValue(func);

// Server delegatable call always returns a pageable object.
if (func.SupportsPaging(node, this))
if ((func is not UserDefinedFunction udf || udf.Binding != null) && func.SupportsPaging(node, this))
{
_isPageable.Set(node.Id, true);
}
Expand Down Expand Up @@ -2261,7 +2261,7 @@ private void SetInfo(CallNode node, CallInfo info, bool markIfAsync = true)
if (function != null)
{
// If the invocation is async then the whole call path is async.
if (markIfAsync && function.IsAsyncInvocation(node, this))
if (markIfAsync && (function is UserDefinedFunction udf && udf.Binding != null) && function.IsAsyncInvocation(node, this))
{
FlagPathAsAsync(node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.PowerFx.Core.App;
Expand All @@ -11,6 +12,7 @@
using Microsoft.PowerFx.Core.Binding.BindInfo;
using Microsoft.PowerFx.Core.Entities;
using Microsoft.PowerFx.Core.Errors;
using Microsoft.PowerFx.Core.Functions.FunctionArgValidators;
using Microsoft.PowerFx.Core.Glue;
using Microsoft.PowerFx.Core.IR;
using Microsoft.PowerFx.Core.IR.Nodes;
Expand All @@ -23,6 +25,7 @@
using Microsoft.PowerFx.Syntax;
using Microsoft.PowerFx.Types;
using static Microsoft.PowerFx.Core.Localization.TexlStrings;
using CallNode = Microsoft.PowerFx.Syntax.CallNode;

namespace Microsoft.PowerFx.Core.Functions
{
Expand All @@ -31,22 +34,42 @@ namespace Microsoft.PowerFx.Core.Functions
/// This includings the binding (and hence IR for evaluation) -
/// This is conceptually immutable after initialization - if the body or signature changes, you need to create a new instance.
/// </summary>
internal class UserDefinedFunction : TexlFunction
internal class UserDefinedFunction : TexlFunction, IExternalPageableSymbol, IExternalDelegatableSymbol
{
private readonly bool _isImperative;
private readonly IEnumerable<UDFArg> _args;
private TexlBinding _binding;

public override bool IsAsync => _binding?.IsAsync(UdfBody) ?? false;
public override bool IsAsync => _binding.IsAsync(UdfBody);

public bool IsPageable => _binding.IsPageable(_binding.Top);

public bool IsDelegatable => _binding.IsDelegatable(_binding.Top);

public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding)
{
Contracts.AssertValue(callNode);
Contracts.AssertValue(binding);
Contracts.Assert(binding.GetInfo(callNode).Function is UserDefinedFunction udf && udf.Binding != null);

return base.IsServerDelegatable(callNode, binding) || IsDelegatable;
}

public override bool SupportsParamCoercion => true;
private const int MaxParameterCount = 30;

private const int MaxParameterCount = 30;

public TexlNode UdfBody { get; }

public override bool IsSelfContained => !_isImperative;

public TexlBinding Binding => _binding;

public bool TryGetExternalDataSource(out IExternalDataSource dataSource)
{
return ArgValidators.DelegatableDataSourceInfoValidator.TryGetValidValue(_binding.Top, _binding, out dataSource);
}

/// <summary>
/// Initializes a new instance of the <see cref="UserDefinedFunction"/> class.
/// </summary>
Expand Down Expand Up @@ -224,24 +247,24 @@ public static IEnumerable<UserDefinedFunction> CreateFunctions(IEnumerable<UDF>
{
errors.Add(new TexlError(udf.Ident, DocumentErrorSeverity.Severe, TexlStrings.ErrUDF_FunctionNameRestricted, udfName));
continue;
}
if (udf.Args.Count > MaxParameterCount)
{
errors.Add(new TexlError(udf.Ident, DocumentErrorSeverity.Severe, TexlStrings.ErrUDF_TooManyParameters, udfName, MaxParameterCount));
continue;
}

if (udf.Args.Count > MaxParameterCount)
{
errors.Add(new TexlError(udf.Ident, DocumentErrorSeverity.Severe, TexlStrings.ErrUDF_TooManyParameters, udfName, MaxParameterCount));
continue;
}

var parametersOk = CheckParameters(udf.Args, errors, nameResolver, out var parameterTypes);
var returnTypeOk = CheckReturnType(udf.ReturnType, errors, nameResolver, out var returnType);
if (!parametersOk || !returnTypeOk)
{
continue;
}
if (nameResolver.Functions.WithName(udfName).Any())
{
errors.Add(new TexlError(udf.Ident, DocumentErrorSeverity.Warning, TexlStrings.WrnUDF_ShadowingBuiltInFunction, udfName));
}

if (nameResolver.Functions.WithName(udfName).Any())
{
errors.Add(new TexlError(udf.Ident, DocumentErrorSeverity.Warning, TexlStrings.WrnUDF_ShadowingBuiltInFunction, udfName));
}

var func = new UserDefinedFunction(udfName.Value, returnType, udf.Body, udf.IsImperative, udf.Args, parameterTypes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerFx.Core;
using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.IR;
using Microsoft.PowerFx.Core.Localization;
using Microsoft.PowerFx.Core.Tests;
Expand Down Expand Up @@ -673,6 +674,39 @@ public void ImperativeUserDefinedFunctionTest(string udfExpression, string expre
Assert.True(expectedError, ex.Message);
Assert.Contains(expectedMethodFailure, ex.StackTrace);
}
}

[Fact]

public void DelegatableUDFTest()
{
var config = new PowerFxConfig();
config.EnableSetFunction();

var schema = DType.CreateTable(
new TypedName(DType.Guid, new DName("ID")),
new TypedName(DType.Number, new DName("Value")));
config.SymbolTable.AddEntity(new TestDataSource("MyDataSource", schema));
config.SymbolTable.AddType(new DName("MyDataSourceTableType"), FormulaType.Build(schema));

var recalcEngine = new RecalcEngine(config);

recalcEngine.AddUserDefinedFunction("A():MyDataSourceTableType = Filter(Sort(MyDataSource,Value), Value > 10);C():MyDataSourceTableType = A();", CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true);
var func = recalcEngine.Functions.WithName("A");

if (func is UserDefinedFunction udf)
{
Assert.True(udf.IsAsync);
Assert.True(udf.IsDelegatable);
}

func = recalcEngine.Functions.WithName("C");

if (func is UserDefinedFunction udf2)
{
Assert.True(udf2.IsAsync);
Assert.True(udf2.IsDelegatable);
}
}

// Binding to inner functions does not impact outer functions.
Expand Down