Add IsDelegatable and IsPageable flags to UDF class (#2459)

These flags will allow the binder to mark a UDF as delegatable/pageable
correctly based on the rule bound to it.
This commit is contained in:
rick-nguyen 2024-08-16 09:02:26 -07:00 коммит произвёл GitHub
Родитель 7c7e66bcea
Коммит 752efe46c4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
3 изменённых файлов: 75 добавлений и 18 удалений

Просмотреть файл

@ -654,7 +654,7 @@ namespace Microsoft.PowerFx.Core.Binding
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;
}
@ -1194,7 +1194,7 @@ namespace Microsoft.PowerFx.Core.Binding
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);
}
@ -2261,7 +2261,7 @@ namespace Microsoft.PowerFx.Core.Binding
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);
}

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.PowerFx.Core.App;
@ -11,6 +12,7 @@ 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.FunctionArgValidators;
using Microsoft.PowerFx.Core.Glue;
using Microsoft.PowerFx.Core.IR;
using Microsoft.PowerFx.Core.IR.Nodes;
@ -23,6 +25,7 @@ using Microsoft.PowerFx.Core.Utils;
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
{
@ -31,13 +34,26 @@ 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;
@ -47,6 +63,13 @@ namespace Microsoft.PowerFx.Core.Functions
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>

Просмотреть файл

@ -9,6 +9,7 @@ using System.Text;
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;
@ -675,6 +676,39 @@ namespace Microsoft.PowerFx.Tests
}
}
[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.
[Fact]
public async Task FunctionInner()