diff --git a/src/libraries/Microsoft.PowerFx.Core/App/IExternalDocumentProperties.cs b/src/libraries/Microsoft.PowerFx.Core/App/IExternalDocumentProperties.cs
index f7aecee0e4..2b3efc4a94 100644
--- a/src/libraries/Microsoft.PowerFx.Core/App/IExternalDocumentProperties.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/App/IExternalDocumentProperties.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
-using System.Collections.Generic;
-
namespace Microsoft.PowerFx.Core.App
{
internal interface IExternalDocumentProperties
diff --git a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs
index 4b399d6444..f0753d525e 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs
@@ -523,8 +523,10 @@ internal static class TexlStrings
public static StringGetter AboutClearCollect = (b) => StringResources.Get("AboutClearCollect", b);
public static StringGetter AboutRemove = (b) => StringResources.Get("AboutRemove", b);
- public static StringGetter RemoveDataSourceArg = (b) => StringResources.Get("RemoveDataSourceArg", b);
- public static StringGetter RemoveRecordsArg = (b) => StringResources.Get("RemoveRecordsArg", b);
+ public static StringGetter RemoveArg1 = (b) => StringResources.Get("RemoveArg1", b);
+ public static StringGetter RemoveArg2 = (b) => StringResources.Get("RemoveArg2", b);
+ public static StringGetter RemoveArg3 = (b) => StringResources.Get("RemoveArg3", b);
+ public static StringGetter RemoveAllArg2 = (b) => StringResources.Get("RemoveAllArg2", b);
public static StringGetter AboutDec2Hex = (b) => StringResources.Get("AboutDec2Hex", b);
public static StringGetter Dec2HexArg1 = (b) => StringResources.Get("Dec2HexArg1", b);
@@ -649,6 +651,7 @@ internal static class TexlStrings
public static ErrorResourceKey ErrBadSchema_ExpectedType = new ErrorResourceKey("ErrBadSchema_ExpectedType");
public static ErrorResourceKey ErrInvalidArgs_Func = new ErrorResourceKey("ErrInvalidArgs_Func");
public static ErrorResourceKey ErrNeedTable_Func = new ErrorResourceKey("ErrNeedTable_Func");
+ public static ErrorResourceKey ErrNeedTable_Arg = new ErrorResourceKey("ErrNeedTable_Arg");
public static ErrorResourceKey ErrNeedTableCol_Func = new ErrorResourceKey("ErrNeedTableCol_Func");
public static ErrorResourceKey ErrNotAccessibleInCurrentContext = new ErrorResourceKey("ErrNotAccessibleInCurrentContext");
public static ErrorResourceKey ErrInternalControlInInputProperty = new ErrorResourceKey("ErrInternalControlInInputProperty");
@@ -853,5 +856,8 @@ internal static class TexlStrings
public static ErrorResourceKey ErrReachedMaxJsonLength = new ErrorResourceKey("ErrReachedMaxJsonLength");
public static ErrorResourceKey ErrUserDefinedTypesDisabled = new ErrorResourceKey("ErrUserDefinedTypesDisabled");
+ public static ErrorResourceKey ErrCollectionDoesNotAcceptThisType = new ErrorResourceKey("ErrCollectionDoesNotAcceptThisType");
+ public static ErrorResourceKey ErrNeedAll = new ErrorResourceKey("ErrNeedAll");
+ public static ErrorResourceKey ErrNeedCollection_Func = new ErrorResourceKey("ErrNeedCollection_Func");
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs
index 4c4ec848fb..2852e0b624 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs
@@ -96,6 +96,11 @@ public sealed class Features
///
internal bool IsUserDefinedTypesEnabled { get; set; } = false;
+ ///
+ /// Enables Remove All delegation.
+ ///
+ internal bool IsRemoveAllDelegationEnabled { get; set; }
+
internal static Features None => new Features();
public static Features PowerFxV1 => new Features
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs
index a8aa0d3ec9..0945ffa3a2 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs
@@ -324,10 +324,15 @@ internal void AddFunction(TexlFunction function)
{
using var guard = _guard.Enter(); // Region is single threaded.
Inc();
- _functions.Add(function);
+ _functions.Add(function);
+
+ if (EnumStoreBuilder == null)
+ {
+ _enumStoreBuilder = new EnumStoreBuilder();
+ }
// Add any associated enums
- EnumStoreBuilder?.WithRequiredEnums(new TexlFunctionSet(function));
+ EnumStoreBuilder.WithRequiredEnums(new TexlFunctionSet(function));
}
internal EnumStoreBuilder EnumStoreBuilder
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs
index fee7d280c7..5b967c555e 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs
@@ -220,7 +220,7 @@ public override async Task> RemoveAsync(IEnumerable> ClearAsync(CancellationToken can
{
return DValue.Of(NotImplementedError(IRContext));
}
-
+
///
/// Patch implementation for derived classes.
///
diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Remove.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Remove.cs
new file mode 100644
index 0000000000..3af7c7b10f
--- /dev/null
+++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Remove.cs
@@ -0,0 +1,419 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.PowerFx.Core.App.ErrorContainers;
+using Microsoft.PowerFx.Core.Binding;
+using Microsoft.PowerFx.Core.Entities;
+using Microsoft.PowerFx.Core.Entities.QueryOptions;
+using Microsoft.PowerFx.Core.Errors;
+using Microsoft.PowerFx.Core.Functions;
+using Microsoft.PowerFx.Core.Functions.DLP;
+using Microsoft.PowerFx.Core.Localization;
+using Microsoft.PowerFx.Core.Logging.Trackers;
+using Microsoft.PowerFx.Core.Types;
+using Microsoft.PowerFx.Core.Types.Enums;
+using Microsoft.PowerFx.Core.Utils;
+using Microsoft.PowerFx.Syntax;
+
+namespace Microsoft.PowerFx.Core.Texl.Builtins
+{
+ internal abstract class RemoveFunctionBase : BuiltinFunction
+ {
+ public override bool ManipulatesCollections => true;
+
+ public override bool ModifiesValues => true;
+
+ public override bool CanSuggestInputColumns => true;
+
+ public override bool IsSelfContained => false;
+
+ public override bool RequiresDataSourceScope => true;
+
+ public override bool SupportsParamCoercion => false;
+
+ public override RequiredDataSourcePermissions FunctionPermission => RequiredDataSourcePermissions.Delete;
+
+ // Return true if this function affects datasource query options.
+ public override bool AffectsDataSourceQueryOptions => true;
+
+ public override bool MutatesArg(int argIndex, TexlNode arg) => argIndex == 0;
+
+ public override bool RequireAllParamColumns => true;
+
+ public RemoveFunctionBase(int arityMax, params DType[] paramTypes)
+ : base("Remove", TexlStrings.AboutRemove, FunctionCategories.Behavior, DType.EmptyTable, 0, 2, arityMax, paramTypes)
+ {
+ }
+
+ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary nodeToCoercedTypeMap)
+ {
+ Contracts.AssertValue(args);
+ Contracts.AssertAllValues(args);
+ Contracts.AssertValue(argTypes);
+ Contracts.Assert(args.Length == argTypes.Length);
+ Contracts.AssertValue(errors);
+ Contracts.Assert(MinArity <= args.Length && args.Length <= MaxArity);
+
+ bool fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
+ Contracts.Assert(returnType.IsTable);
+
+ DType collectionType = argTypes[0];
+ if (!collectionType.IsTable)
+ {
+ fValid = false;
+ errors.EnsureError(args[0], TexlStrings.ErrNeedCollection_Func, Name);
+ }
+
+ // !!!TODO New stuff. In case this doesnt work, delete from here to the end of the function
+ var argCount = argTypes.Count();
+
+ for (int i = 1; i < argCount; i++)
+ {
+ var argType = argTypes[i];
+ var arg = args[i];
+
+ if (!argType.IsAggregate)
+ {
+ if (argCount >= 3 && i == argCount - 1)
+ {
+ fValid = (context.Features.StronglyTypedBuiltinEnums && BuiltInEnums.RemoveFlagsEnum.FormulaType._type.Accepts(argType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules)) ||
+ (!context.Features.StronglyTypedBuiltinEnums && DType.String.Accepts(argType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules));
+
+ if (!fValid)
+ {
+ errors.EnsureError(DocumentErrorSeverity.Severe, arg, TexlStrings.ErrRemoveAllArg);
+ }
+ }
+ else
+ {
+ fValid = false;
+ errors.EnsureError(args[i], TexlStrings.ErrNeedRecord_Arg, args[i]);
+ }
+
+ continue;
+ }
+
+ var collectionAcceptsRecord = collectionType.Accepts(argType.ToTable(), exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules);
+ var recordAcceptsCollection = argType.ToTable().Accepts(collectionType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules);
+
+ // PFxV1 is more restrictive than PA in terms of column matching. If the collection does not accept the record or vice versa, it is an error.
+ if ((context.Features.PowerFxV1CompatibilityRules && (!collectionAcceptsRecord || !recordAcceptsCollection)) ||
+ (!context.Features.PowerFxV1CompatibilityRules && (!collectionAcceptsRecord && !recordAcceptsCollection)))
+ {
+ fValid = false;
+ SetErrorForMismatchedColumns(collectionType, argType, arg, errors, context.Features);
+ }
+
+ // Only warn about no-op record inputs if there are no data sources that would use reference identity for comparison.
+ else if (!collectionType.AssociatedDataSources.Any() && !recordAcceptsCollection)
+ {
+ errors.EnsureError(DocumentErrorSeverity.Warning, args[i], TexlStrings.ErrCollectionDoesNotAcceptThisType);
+ }
+ }
+
+ returnType = context.Features.PowerFxV1CompatibilityRules ? DType.Void : collectionType;
+
+ return fValid;
+ }
+
+ public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
+ {
+ base.CheckSemantics(binding, args, argTypes, errors);
+ base.ValidateArgumentIsMutable(binding, args[0], errors);
+ MutationUtils.CheckSemantics(binding, this, args, argTypes, errors);
+ }
+
+ public override IEnumerable GetRequiredEnumNames()
+ {
+ return new List() { LanguageConstants.RemoveFlagsEnumString };
+ }
+
+ public override bool IsLazyEvalParam(TexlNode node, int index, Features features)
+ {
+ // First argument to mutation functions is Lazy for datasources that are copy-on-write.
+ // If there are any side effects in the arguments, we want those to have taken place before we make the copy.
+ return index == 0;
+ }
+
+ public override IEnumerable GetIdentifierOfModifiedValue(TexlNode[] args, out TexlNode identifierNode)
+ {
+ Contracts.AssertValue(args);
+
+ identifierNode = null;
+ if (args.Length == 0)
+ {
+ return null;
+ }
+
+ var firstNameNode = args[0]?.AsFirstName();
+ identifierNode = firstNameNode;
+ if (firstNameNode == null)
+ {
+ return null;
+ }
+
+ var identifiers = new List
+ {
+ firstNameNode.Ident
+ };
+
+ return identifiers;
+ }
+
+ public override bool IsAsyncInvocation(CallNode callNode, TexlBinding binding)
+ {
+ Contracts.AssertValue(callNode);
+ Contracts.AssertValue(binding);
+
+ return Arg0RequiresAsync(callNode, binding);
+ }
+ }
+
+ // Remove(collection:*[], item1:![], item2:![], ..., ["All"])
+ internal class RemoveFunction : RemoveFunctionBase, ISuggestionAwareFunction
+ {
+ public bool CanSuggestThisItem => true;
+
+ public override bool ArgMatchesDatasourceType(int argNum)
+ {
+ return argNum >= 1;
+ }
+
+ public override bool TryGetTypeForArgSuggestionAt(int argIndex, out DType type)
+ {
+ if (argIndex > 0)
+ {
+ type = default;
+ return false;
+ }
+
+ return base.TryGetTypeForArgSuggestionAt(argIndex, out type);
+ }
+
+ public RemoveFunction()
+ : base(int.MaxValue, DType.EmptyTable)
+ {
+ }
+
+ public override IEnumerable GetSignatures()
+ {
+ yield return new[] { TexlStrings.RemoveArg1, TexlStrings.RemoveArg2 };
+ yield return new[] { TexlStrings.RemoveArg1, TexlStrings.RemoveArg2, TexlStrings.RemoveArg2 };
+ yield return new[] { TexlStrings.RemoveArg1, TexlStrings.RemoveArg2, TexlStrings.RemoveArg2, TexlStrings.RemoveArg2 };
+ }
+
+ public override IEnumerable GetSignatures(int arity)
+ {
+ if (arity > 2)
+ {
+ return GetGenericSignatures(arity, TexlStrings.RemoveArg1, TexlStrings.RemoveArg2);
+ }
+
+ return base.GetSignatures(arity);
+ }
+
+ // This method returns true if there are special suggestions for a particular parameter of the function.
+ public override bool HasSuggestionsForParam(int argumentIndex)
+ {
+ Contracts.Assert(argumentIndex >= 0);
+
+ return argumentIndex != 1;
+ }
+
+ ///
+ /// As Remove uses the source record in it's entirety to find the entry in table, uses deepcompare at runtime, we need all fields from source.
+ /// So update the selects for all columns in the source in this case except when datasource is pageable.
+ /// In that case, we can get the info at runtime.
+ ///
+ public override bool UpdateDataQuerySelects(CallNode callNode, TexlBinding binding, DataSourceToQueryOptionsMap dataSourceToQueryOptionsMap)
+ {
+ Contracts.AssertValue(callNode);
+ Contracts.AssertValue(binding);
+
+ if (!CheckArgsCount(callNode, binding))
+ {
+ return false;
+ }
+
+ var args = Contracts.VerifyValue(callNode.Args.Children);
+
+ DType dsType = binding.GetType(args[0]);
+ if (dsType.AssociatedDataSources == null
+ || dsType == DType.EmptyTable)
+ {
+ return false;
+ }
+
+ var sourceRecordType = binding.GetType(args[1]);
+
+ // This might be the case where Remove(CDS, Gallery.Selected)
+ if (sourceRecordType == DType.EmptyRecord)
+ {
+ return false;
+ }
+
+ var firstTypeName = sourceRecordType.GetNames(DPath.Root).FirstOrDefault();
+
+ if (!firstTypeName.IsValid)
+ {
+ return false;
+ }
+
+ DType type = firstTypeName.Type;
+ DName columnName = firstTypeName.Name;
+
+ // This might be the case where Remove(CDS, Gallery.Selected)
+ if (!dsType.Contains(columnName))
+ {
+ return false;
+ }
+
+ dsType.AssociateDataSourcesToSelect(
+ dataSourceToQueryOptionsMap,
+ columnName,
+ type,
+ false /*skipIfNotInSchema*/,
+ true); /*skipExpands*/
+
+ return true;
+ }
+
+ // This method filters for a table as the first parameter, records as intermediate parameters
+ // and a string (First/All) as the final parameter.
+ public override bool IsSuggestionTypeValid(int paramIndex, DType type)
+ {
+ Contracts.Assert(paramIndex >= 0);
+ Contracts.AssertValid(type);
+
+ if (paramIndex >= MaxArity)
+ {
+ return false;
+ }
+
+ if (paramIndex == 0)
+ {
+ return type.IsTable;
+ }
+
+ // String suggestions for column names may occur within the context of a record
+ return type.IsRecord || type.Kind == DKind.String;
+ }
+
+ public override bool IsAsyncInvocation(CallNode callNode, TexlBinding binding)
+ {
+ Contracts.AssertValue(callNode);
+ Contracts.AssertValue(binding);
+
+ return Arg0RequiresAsync(callNode, binding);
+ }
+
+ protected override bool RequiresPagedDataForParamCore(TexlNode[] args, int paramIndex, TexlBinding binding)
+ {
+ Contracts.AssertValue(args);
+ Contracts.AssertAllValues(args);
+ Contracts.Assert(paramIndex >= 0 && paramIndex < args.Length);
+ Contracts.AssertValue(binding);
+ Contracts.Assert(binding.IsPageable(Contracts.VerifyValue(args[paramIndex])));
+
+ // For the first argument, we need only metadata. No actual data from datasource is required.
+ return paramIndex > 0;
+ }
+ }
+
+ // Remove(collection:*[], source:*[], ["All"])
+ internal class RemoveAllFunction : RemoveFunctionBase
+ {
+ public override bool ArgMatchesDatasourceType(int argNum)
+ {
+ return argNum == 1;
+ }
+
+ public RemoveAllFunction()
+ : base(3, DType.EmptyTable, DType.EmptyTable, DType.String)
+ {
+ }
+
+ public override IEnumerable GetSignatures()
+ {
+ yield return new[] { TexlStrings.RemoveArg1, TexlStrings.RemoveAllArg2 };
+ yield return new[] { TexlStrings.RemoveArg1, TexlStrings.RemoveAllArg2, TexlStrings.RemoveArg3 };
+ }
+
+ public override bool IsLazyEvalParam(TexlNode node, int index, Features features)
+ {
+ // First argument to mutation functions is Lazy for datasources that are copy-on-write.
+ // If there are any side effects in the arguments, we want those to have taken place before we make the copy.
+ return index == 0;
+ }
+
+ // This method returns true if there are special suggestions for a particular parameter of the function.
+ public override bool HasSuggestionsForParam(int argumentIndex)
+ {
+ Contracts.Assert(argumentIndex >= 0);
+
+ return argumentIndex == 0;
+ }
+
+ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding)
+ {
+ Contracts.AssertValue(callNode);
+ Contracts.AssertValue(binding);
+
+ if (!CheckArgsCount(callNode, binding))
+ {
+ return false;
+ }
+
+ // Use ECS flag as a guard.
+ if (binding.Document != null)
+ {
+ if (!binding.Features.IsRemoveAllDelegationEnabled)
+ {
+ return false;
+ }
+ }
+
+ if (!binding.TryGetDataSourceInfo(callNode.Args.Children[0], out IExternalDataSource dataSource))
+ {
+ return false;
+ }
+
+ // Currently we delegate only to CDS. This is the first version of the feature and not a limitation of other datasources
+ if (dataSource == null || dataSource?.Kind != DataSourceKind.CdsNative)
+ {
+ TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.DataSourceNotDelegatable, callNode, binding, this);
+ return false;
+ }
+
+ // Right now we delegate only if the set of records is a table/queried table to mitigate the performance impact of the remove operation.
+ // Deleting single records (via Lookup) does not have the same performance impact
+ var dsType = binding.GetType(callNode.Args.Children[1]).Kind;
+ if (dsType != DKind.Table)
+ {
+ TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.InvalidArgType, callNode, binding, this);
+ return false;
+ }
+
+ TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.DelegationSuccessful, callNode, binding, this);
+ return true;
+ }
+
+ public override bool SupportsPaging(CallNode callNode, TexlBinding binding)
+ {
+ if (!binding.TryGetDataSourceInfo(callNode.Args.Children[0], out IExternalDataSource dataSource))
+ {
+ return false;
+ }
+
+ // Currently we delegate only to CDS. This is the first version of the feature and not a limitation of other datasources
+ if (dataSource == null || dataSource?.Kind == DataSourceKind.CdsNative)
+ {
+ return false;
+ }
+
+ return base.SupportsPaging(callNode, binding);
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Types/Enums/BuiltInEnums.cs b/src/libraries/Microsoft.PowerFx.Core/Types/Enums/BuiltInEnums.cs
index 1974d0dedf..cdf0b90ba0 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Types/Enums/BuiltInEnums.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Types/Enums/BuiltInEnums.cs
@@ -199,5 +199,15 @@ private static Dictionary TraceSeverityDictionary()
{ "IgnoreUnsupportedTypes", TraceFunction.IgnoreUnsupportedTypesEnumValue },
},
canConcatenateStronglyTyped: true);
+
+ public static readonly EnumSymbol RemoveFlagsEnum = new EnumSymbol(
+ new DName(LanguageConstants.RemoveFlagsEnumString),
+ DType.String,
+ new Dictionary()
+ {
+ { "First", "first" },
+ { "All", "all" },
+ },
+ canCoerceFromBackingKind: true);
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs b/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs
index ff4403bc2e..3a849d9fb8 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs
@@ -30,6 +30,7 @@ internal sealed class EnumStoreBuilder
{ LanguageConstants.JSONFormatEnumString, BuiltInEnums.JSONFormatEnum },
{ LanguageConstants.TraceSeverityEnumString, BuiltInEnums.TraceSeverityEnum },
{ LanguageConstants.TraceOptionsEnumString, BuiltInEnums.TraceOptionsEnum },
+ { LanguageConstants.RemoveFlagsEnumString, BuiltInEnums.RemoveFlagsEnum },
};
// DefaultEnums, with enum strings, is legacy and only used by Power Apps
@@ -79,6 +80,10 @@ internal sealed class EnumStoreBuilder
{
LanguageConstants.TraceOptionsEnumString,
$"%s[{string.Join(", ", BuiltInEnums.TraceOptionsEnum.EnumType.ValueTree.GetPairs().Select(pair => $@"{pair.Key}:""{pair.Value.Object}"""))}]"
+ },
+ {
+ LanguageConstants.RemoveFlagsEnumString,
+ $"%s[{string.Join(", ", BuiltInEnums.RemoveFlagsEnum.EnumType.ValueTree.GetPairs().Select(pair => $@"{pair.Key}:""{pair.Value.Object}"""))}]"
}
};
#endregion
diff --git a/src/libraries/Microsoft.PowerFx.Core/Utils/LanguageConstants.cs b/src/libraries/Microsoft.PowerFx.Core/Utils/LanguageConstants.cs
index cfeb1ce077..e6efc8b05d 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Utils/LanguageConstants.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Utils/LanguageConstants.cs
@@ -75,5 +75,10 @@ internal class LanguageConstants
/// The string value representing the JSON format.
///
internal const string JSONFormatEnumString = "JSONFormat";
+
+ ///
+ /// The string value representing the remove flag option.
+ ///
+ public const string RemoveFlagsEnumString = "RemoveFlags";
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs
index 69db48a8a1..a881f43420 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs
@@ -49,7 +49,8 @@ public static void EnableMutationFunctions(this SymbolTable symbolTable)
symbolTable.AddFunction(new PatchSingleRecordImpl());
symbolTable.AddFunction(new PatchAggregateImpl());
symbolTable.AddFunction(new PatchAggregateSingleTableImpl());
- symbolTable.AddFunction(new RemoveFunction());
+ symbolTable.AddFunction(new RemoveImpl());
+ symbolTable.AddFunction(new RemoveAllImpl());
symbolTable.AddFunction(new ClearImpl());
symbolTable.AddFunction(new ClearCollectImpl());
symbolTable.AddFunction(new ClearCollectScalarImpl());
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryMutation.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryMutation.cs
index bbb726a7ca..cc8c2599d5 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryMutation.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryMutation.cs
@@ -402,4 +402,22 @@ public async Task InvokeAsync(FormulaType irContext, FormulaValue[
return await new ClearCollectImpl().InvokeAsync(irContext, args, cancellationToken).ConfigureAwait(false);
}
}
+
+ // Remove(collection:*[], item1:![], item2:![], ..., ["All"])
+ internal class RemoveImpl : RemoveFunction, IAsyncTexlFunction3
+ {
+ public async Task InvokeAsync(FormulaType irContext, FormulaValue[] args, CancellationToken cancellationToken)
+ {
+ return await MutationUtils.RemoveCore(irContext, args, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ // Remove(collection:*[], source:*[], ["All"])
+ internal class RemoveAllImpl : RemoveAllFunction, IAsyncTexlFunction3
+ {
+ public async Task InvokeAsync(FormulaType irContext, FormulaValue[] args, CancellationToken cancellationToken)
+ {
+ return await MutationUtils.RemoveCore(irContext, args, cancellationToken).ConfigureAwait(false);
+ }
+ }
}
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/MutationUtils.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/MutationUtils.cs
index b970442c79..75c8262217 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/MutationUtils.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/MutationUtils.cs
@@ -3,12 +3,10 @@
using System.Collections.Generic;
using System.Linq;
-using Microsoft.PowerFx.Core.App.ErrorContainers;
-using Microsoft.PowerFx.Core.Entities;
-using Microsoft.PowerFx.Core.Errors;
-using Microsoft.PowerFx.Core.Localization;
-using Microsoft.PowerFx.Core.Types;
-using Microsoft.PowerFx.Syntax;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.PowerFx.Core.IR;
+using Microsoft.PowerFx.Interpreter.Localization;
using Microsoft.PowerFx.Types;
namespace Microsoft.PowerFx.Interpreter
@@ -47,5 +45,89 @@ public static DValue MergeRecords(IEnumerable records
return DValue.Of(FormulaValue.NewRecordFromFields(mergedFields.Select(kvp => new NamedValue(kvp.Key, kvp.Value))));
}
+
+ public static async Task RemoveCore(FormulaType irContext, FormulaValue[] args, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ FormulaValue arg0;
+
+ if (args[0] is LambdaFormulaValue arg0lazy)
+ {
+ arg0 = await arg0lazy.EvalAsync().ConfigureAwait(false);
+ }
+ else
+ {
+ arg0 = args[0];
+ }
+
+ if (arg0 is BlankValue || arg0 is ErrorValue)
+ {
+ return arg0;
+ }
+
+ // If any of the argN (N>0) is error, return the error.
+ foreach (var arg in args.Skip(1))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (arg is ErrorValue)
+ {
+ return arg;
+ }
+
+ if (arg is TableValue tableValue)
+ {
+ var errorRecord = tableValue.Rows.First(row => row.IsError);
+ if (errorRecord != null)
+ {
+ return errorRecord.Error;
+ }
+ }
+ }
+
+ var all = false;
+ var datasource = (TableValue)arg0;
+
+ if (args.Count() >= 3 && args.Last() is OptionSetValue opv)
+ {
+ all = opv.Option == "All";
+ }
+
+ List recordsToRemove = null;
+
+ if (args[1] is TableValue sourceTable)
+ {
+ recordsToRemove = sourceTable.Rows.Select(row => row.Value).ToList();
+ }
+ else
+ {
+ recordsToRemove = args
+ .Skip(1)
+ .Where(arg => arg is RecordValue)
+ .OfType()
+ .ToList();
+ }
+
+ // At this point all errors have been handled.
+ var response = await datasource.RemoveAsync(recordsToRemove, all, cancellationToken).ConfigureAwait(false);
+
+ if (response.IsError)
+ {
+ var errors = new List();
+ foreach (var error in response.Error.Errors)
+ {
+ errors.Add(new ExpressionError()
+ {
+ ResourceKey = RuntimeStringResources.ErrRecordNotFound,
+ Kind = ErrorKind.NotFound
+ });
+ }
+
+ return new ErrorValue(IRContext.NotInSource(irContext), errors);
+ }
+
+ return irContext == FormulaType.Void ? FormulaValue.NewVoid() : FormulaValue.NewBlank();
+ }
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/RemoveFunction.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/RemoveFunction.cs
deleted file mode 100644
index e0bb724273..0000000000
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/RemoveFunction.cs
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.PowerFx.Core.App.ErrorContainers;
-using Microsoft.PowerFx.Core.Binding;
-using Microsoft.PowerFx.Core.Errors;
-using Microsoft.PowerFx.Core.Functions;
-using Microsoft.PowerFx.Core.Localization;
-using Microsoft.PowerFx.Core.Types;
-using Microsoft.PowerFx.Core.Utils;
-using Microsoft.PowerFx.Syntax;
-using Microsoft.PowerFx.Types;
-using static Microsoft.PowerFx.Core.Localization.TexlStrings;
-using static Microsoft.PowerFx.Syntax.PrettyPrintVisitor;
-
-namespace Microsoft.PowerFx.Functions
-{
- internal abstract class RemoveFunctionBase : BuiltinFunction
- {
- public override bool IsSelfContained => false;
-
- public override bool RequiresDataSourceScope => true;
-
- public override bool CanSuggestInputColumns => true;
-
- public override bool ManipulatesCollections => true;
-
- public override bool ArgMatchesDatasourceType(int argNum)
- {
- return argNum >= 1;
- }
-
- public override bool MutatesArg(int argIndex, TexlNode arg) => argIndex == 0;
-
- public override bool IsLazyEvalParam(TexlNode node, int index, Features features)
- {
- // First argument to mutation functions is Lazy for datasources that are copy-on-write.
- // If there are any side effects in the arguments, we want those to have taken place before we make the copy.
- return index == 0;
- }
-
- public RemoveFunctionBase(DPath theNamespace, string name, StringGetter description, FunctionCategories fc, DType returnType, BigInteger maskLambdas, int arityMin, int arityMax, params DType[] paramTypes)
- : base(theNamespace, name, /*localeSpecificName*/string.Empty, description, fc, returnType, maskLambdas, arityMin, arityMax, paramTypes)
- {
- }
-
- public RemoveFunctionBase(string name, StringGetter description, FunctionCategories fc, DType returnType, BigInteger maskLambdas, int arityMin, int arityMax, params DType[] paramTypes)
- : this(DPath.Root, name, description, fc, returnType, maskLambdas, arityMin, arityMax, paramTypes)
- {
- }
-
- protected static bool CheckArgs(FormulaValue[] args, out FormulaValue faultyArg)
- {
- // If any args are error, propagate up.
- foreach (var arg in args)
- {
- if (arg is ErrorValue)
- {
- faultyArg = arg;
-
- return false;
- }
- }
-
- faultyArg = null;
-
- return true;
- }
- }
-
- internal class RemoveFunction : RemoveFunctionBase, IAsyncTexlFunction3
- {
- public override bool IsSelfContained => false;
-
- public override bool TryGetTypeForArgSuggestionAt(int argIndex, out DType type)
- {
- if (argIndex == 1)
- {
- type = default;
- return false;
- }
-
- return base.TryGetTypeForArgSuggestionAt(argIndex, out type);
- }
-
- public RemoveFunction()
- : base("Remove", AboutRemove, FunctionCategories.Table | FunctionCategories.Behavior, DType.Unknown, 0, 2, int.MaxValue, DType.EmptyTable, DType.EmptyRecord)
- {
- }
-
- public override IEnumerable GetSignatures()
- {
- yield return new[] { RemoveDataSourceArg, RemoveRecordsArg };
- yield return new[] { RemoveDataSourceArg, RemoveRecordsArg, RemoveRecordsArg };
- }
-
- public override IEnumerable GetSignatures(int arity)
- {
- if (arity > 2)
- {
- return GetGenericSignatures(arity, RemoveDataSourceArg, RemoveRecordsArg, RemoveRecordsArg);
- }
-
- return base.GetSignatures(arity);
- }
-
- public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary nodeToCoercedTypeMap)
- {
- Contracts.AssertValue(args);
- Contracts.AssertAllValues(args);
- Contracts.AssertValue(argTypes);
- Contracts.Assert(args.Length == argTypes.Length);
- Contracts.AssertValue(errors);
- Contracts.Assert(MinArity <= args.Length && args.Length <= MaxArity);
-
- var fValid = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
-
- DType collectionType = argTypes[0];
- if (!collectionType.IsTable)
- {
- errors.EnsureError(args[0], ErrNeedTable_Func, Name);
- fValid = false;
- }
-
- var argCount = argTypes.Length;
-
- for (var i = 1; i < argCount; i++)
- {
- DType argType = argTypes[i];
-
- // The subsequent args should all be records.
- if (!argType.IsRecord)
- {
- // The last arg may be the optional "ALL" parameter.
- if (argCount >= 3 && i == argCount - 1 && DType.String.Accepts(argType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules))
- {
- var strNode = (StrLitNode)args[i];
-
- if (strNode.Value.ToUpperInvariant() != "ALL")
- {
- fValid = false;
- errors.EnsureError(args[i], ErrRemoveAllArg, args[i]);
- }
-
- continue;
- }
-
- fValid = false;
- errors.EnsureError(args[i], ErrNeedRecord, args[i]);
- continue;
- }
-
- var collectionAcceptsRecord = collectionType.Accepts(argType.ToTable(), exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules);
- var recordAcceptsCollection = argType.ToTable().Accepts(collectionType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules);
-
- var featuresWithPFxV1RulesDisabled = new Features(context.Features) { PowerFxV1CompatibilityRules = false };
- bool checkAggregateNames = argType.CheckAggregateNames(collectionType, args[i], errors, featuresWithPFxV1RulesDisabled, SupportsParamCoercion);
-
- // The item schema should be compatible with the collection schema.
- if (!checkAggregateNames)
- {
- fValid = false;
- if (!SetErrorForMismatchedColumns(collectionType, argType, args[i], errors, context.Features))
- {
- errors.EnsureError(DocumentErrorSeverity.Severe, args[i], ErrTableDoesNotAcceptThisType);
- }
- }
- }
-
- returnType = context.Features.PowerFxV1CompatibilityRules ? DType.Void : collectionType;
-
- return fValid;
- }
-
- public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
- {
- base.CheckSemantics(binding, args, argTypes, errors);
- base.ValidateArgumentIsMutable(binding, args[0], errors);
- }
-
- public async Task InvokeAsync(FormulaType irContextRet, FormulaValue[] args, CancellationToken cancellationToken)
- {
- var validArgs = CheckArgs(args, out FormulaValue faultyArg);
-
- if (!validArgs)
- {
- return faultyArg;
- }
-
- var arg0lazy = (LambdaFormulaValue)args[0];
- var arg0 = await arg0lazy.EvalAsync().ConfigureAwait(false);
-
- if (arg0 is BlankValue)
- {
- return arg0;
- }
-
- var argCount = args.Count();
- var lastArg = args.Last() as FormulaValue;
- var all = false;
- var toExclude = 1;
-
- if (argCount >= 3 && DType.String.Accepts(lastArg.Type._type, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: true))
- {
- var lastArgValue = (string)lastArg.ToObject();
-
- if (lastArgValue.ToUpperInvariant() == "ALL")
- {
- all = true;
- toExclude = 2;
- }
- }
-
- var datasource = (TableValue)arg0;
- var recordsToRemove = args.Skip(1).Take(args.Length - toExclude);
-
- cancellationToken.ThrowIfCancellationRequested();
- var ret = await datasource.RemoveAsync(recordsToRemove, all, cancellationToken).ConfigureAwait(false);
-
- // If the result is an error, propagate it up. else return blank.
- FormulaValue result;
- if (ret.IsError)
- {
- result = FormulaValue.NewError(ret.Error.Errors, irContextRet == FormulaType.Void ? FormulaType.Void : FormulaType.Blank);
- }
- else
- {
- result = irContextRet == FormulaType.Void ? FormulaValue.NewVoid() : FormulaValue.NewBlank();
- }
-
- return result;
- }
- }
-}
diff --git a/src/strings/PowerFxResources.en-US.resx b/src/strings/PowerFxResources.en-US.resx
index a745ad5abb..7663feb2f1 100644
--- a/src/strings/PowerFxResources.en-US.resx
+++ b/src/strings/PowerFxResources.en-US.resx
@@ -1997,6 +1997,10 @@
Property expects a required parameter. Please use parentheses to pass the required parameter.
Error Message.
+
+ Cannot use a non-table value in this context: '{0}'.
+ Error Message.
+
The first argument of '{0}' should be a one-column table.
Error Message.
@@ -3290,16 +3294,36 @@
function_parameter - Second argument to the Patch function - the updates to be applied to the given rows.
- Removes a specific record or records from a data source
+ Removes (optionally All) items from the specified 'collection'.
Description of 'Remove' function.
-
- data_source
- function_parameter - First parameter for the Remove function. The data source that contains the records that you want to remove from. Translate this string. When translating, maintain as a single word (i.e., do not add spaces).
+
+ collection
+ function_parameter - First parameter of the Remove function - the name of the collection to have an item removed.
+
+
+ item
+ function_parameter - Second parameter to the Remove function - the item to be removed.
+
+
+ The collection to remove rows from.
-
- remove_record(s)
- function_parameter - One or more records to be removed. Translate this string. When translating, maintain as a single word (i.e., do not add spaces).
+
+ A record value specifying the row to remove.
+
+
+ source
+ function_parameter - Second parameter to the RemoveAll function - the source of the elements to be removed.
+
+
+ A table value that specifies multiple rows to remove from the given collection.
+
+
+ all
+ function_parameter - Third argument for the Remove function - value indicating whether to remove all matches or only the first one.
+
+
+ An optional argument that specifies whether to remove all matches, or just the first one.
If provided, last argument must be 'RemoveFlags.All'. Is there a typo?
@@ -4822,6 +4846,10 @@
The source to clear.
+
+ The first argument of '{0}' should be a collection.
+ Error Message.
+
The specified data source cannot be used with this function.
Error Message.
@@ -4846,4 +4874,56 @@
The 'User-defined types' experimental feature is not enabled.
Error message returned when user uses User-defined types with the User-defined types feature flag turned off.
+
+ Incompatible type. The collection can't contain values of this type.
+ Error Message.
+
+
+ You might need to convert the type of the item using, for example, a Datevalue, Text, or Value function.
+ 1 How to fix the error.
+
+
+ Article: Create and update a collection in a canvas app
+ Article: Create and update a collection in a canvas app.
+
+
+ https://go.microsoft.com/fwlink/?linkid=722609
+ {Locked}
+
+
+ Module: Use basic formulas
+ 3 crown link on basic formulas
+
+
+ https://go.microsoft.com/fwlink/?linkid=2132396
+ {Locked}
+
+
+ Module: Author basic formulas with tables and records
+ 3 crown link on tables and records
+
+
+ https://go.microsoft.com/fwlink/?linkid=2132700
+ {Locked}
+
+
+ Incorrect argument. This formula expects an optional 'All' argument or no argument.
+ Error Message.
+
+
+ If you’re using an Update function, for example, you might supply an optional 'All' argument at the end of the formula. This feature is available because a record might occur more than once (e.g., in a collection) to make sure that all copies of a record are updated.
+ 1 How to fix the error.
+
+
+ Either supply the optional 'All' argument or remove it.
+ 1 How to fix the error.
+
+
+ Article: Formula reference for Power Apps
+ Article: Formula reference for Power Apps
+
+
+ https://go.microsoft.com/fwlink/?linkid=2132478
+ {Locked}
+
\ No newline at end of file
diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt
index 72415f84fe..3e2fa529f2 100644
--- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt
+++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt
@@ -1,11 +1,11 @@
-#SETUP: PowerFxV1CompatibilityRules,EnableExpressionChaining,MutationFunctionsTestSetup
+#SETUP: PowerFxV1CompatibilityRules,EnableExpressionChaining,StronglyTypedBuiltinEnums,MutationFunctionsTestSetup
// Check MutationFunctionsTestSetup handler (PowerFxEvaluationTests.cs) for documentation.
>> Collect(t1, r2);Remove(t1, r1);t1
Table({Field1:2,Field2:"moon",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false})
->> Collect(t1, r1);Collect(t1, r1);Collect(t1, r1);Collect(t1, r2);Remove(t1, r1, "All");t1
+>> Collect(t1, r1);Collect(t1, r1);Collect(t1, r1);Collect(t1, r2);Remove(t1, r1, RemoveFlags.All);t1
Table({Field1:2,Field2:"moon",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false})
>> Collect(t1, r2);
@@ -13,26 +13,26 @@ Table({Field1:2,Field2:"moon",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false})
Collect(t1, {Field1:4,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
Remove(t1, {Field4:false});
t1
-Error({Kind:ErrorKind.NotFound})
+Errors: Error 200-206: The function 'Remove' has some invalid arguments.|Error 211-225: Missing column. Your formula is missing a column 'Field1' with a type of 'Decimal'.
>> Collect(t1, r2);
Collect(t1, {Field1:3,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
Collect(t1, {Field1:4,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
- Remove(t1, {Field4:false}, "All");
+ Remove(t1, {Field4:false}, RemoveFlags.All);
t1
Error({Kind:ErrorKind.NotFound})
>> Collect(t1, r2);
Collect(t1, {Field1:1/0,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
Collect(t1, {Field1:1/0,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
- Remove(t1, {Field1:1/0,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:true}, "All");
+ Remove(t1, {Field1:1/0,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:true}, RemoveFlags.All);
t1
Error({Kind:ErrorKind.NotFound})
>> Collect(t1, {Field1:1/0,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
Collect(t1, {Field1:1/0,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
Collect(t1, {Field1:1/0,Field2:"moon",Field3:DateTime(2030,2,1,0,0,0,0),Field4:true});
- Remove(t1, {Field2:"earth"}, {Field2:"moon"}, "All");
+ Remove(t1, {Field2:"earth"}, {Field2:"moon"}, RemoveFlags.All);
t1
Error(Table({Kind:ErrorKind.NotFound},{Kind:ErrorKind.NotFound}))
@@ -46,7 +46,7 @@ Table({Field1:1,Field2:"earth",Field3:DateTime(2022,1,1,0,0,0,0),Field4:true},{F
>> Collect(t1, r2);
Collect(t1, {Field1:3,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
Collect(t1, {Field1:3,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
- Remove(t1, {DisplayNameField1:3,DisplayNameField2:"earth",DisplayNameField3:DateTime(2022,2,1,0,0,0,0),DisplayNameField4:false}, "All");
+ Remove(t1, {DisplayNameField1:3,DisplayNameField2:"earth",DisplayNameField3:DateTime(2022,2,1,0,0,0,0),DisplayNameField4:false}, RemoveFlags.All);
t1
Table({Field1:1,Field2:"earth",Field3:DateTime(2022,1,1,0,0,0,0),Field4:true},{Field1:2,Field2:"moon",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false})
@@ -54,43 +54,37 @@ Table({Field1:1,Field2:"earth",Field3:DateTime(2022,1,1,0,0,0,0),Field4:true},{F
Table({Field1:2,Field2:{Field2_1:221,Field2_2:"2_2",Field2_3:{Field2_3_1:2231,Field2_3_2:"common"}},Field3:false},{Field1:3,Field2:{Field2_1:321,Field2_2:"2_2",Field2_3:{Field2_3_1:3231,Field2_3_2:"common"}},Field3:true})
>> Remove(t2, {Field1:1,Field2:{Field2_1:121,Field2_2:"2_2",Field2_3:{Field2_3_1:1231}},Field3:false}); t2
-Errors: Error 11-98: Invalid argument type. Expecting a Record value, but of a different schema.|Error 11-98: Missing column. Your formula is missing a column 'DisplayNameField2_3.DisplayNameField2_3_2' with a type of 'Text'.|Error 0-6: The function 'Remove' has some invalid arguments.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-98: Missing column. Your formula is missing a column 'DisplayNameField2.DisplayNameField2_3.Field2_3_2' with a type of 'Text'.
>> Remove(t2, {Field1:2,Field2:{Field2_1:221,Field2_3:{Field2_3_1:2231,Field2_3_2:"common"}},Field3:false}); t2
-Errors: Error 11-103: Invalid argument type. Expecting a Record value, but of a different schema.|Error 11-103: Missing column. Your formula is missing a column 'DisplayNameField2_2' with a type of 'Text'.|Error 0-6: The function 'Remove' has some invalid arguments.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-103: Missing column. Your formula is missing a column 'DisplayNameField2.Field2_2' with a type of 'Text'.
>> Remove(t2, {Field2:{Field2_1:321, Field2_2:"Moon"}}); t2
-Errors: Error 11-51: Invalid argument type. Expecting a Record value, but of a different schema.|Error 11-51: Missing column. Your formula is missing a column 'DisplayNameField2_3' with a type of 'Record'.|Error 0-6: The function 'Remove' has some invalid arguments.
-
->> Remove(t2, {Field2:{Field2_3:{Field2_3_1:3231}}}) ; t2
-Errors: Error 11-48: Invalid argument type. Expecting a Record value, but of a different schema.|Error 11-48: Missing column. Your formula is missing a column 'DisplayNameField2_1' with a type of 'Decimal'.|Error 0-6: The function 'Remove' has some invalid arguments.
-
->> Remove(t2, {Field2:{Field2_1:321,Field2_2:"2_2",Field2_3:{Field2_3_1:3231,Field2_3_2:"common"}}}); t2
-Error({Kind:ErrorKind.NotFound})
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-51: Missing column. Your formula is missing a column 'Field1' with a type of 'Decimal'.
>> Remove(t2, {Field1:5})
-Error({Kind:ErrorKind.NotFound})
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-21: Missing column. Your formula is missing a column 'Field2' with a type of 'Record'.
->> Remove(t2, {Field2:{Field2_1:555,Field2_2:"2_2",Field2_3:{Field2_3_1:3231,Field2_3_2:"common"}}});
-Error({Kind:ErrorKind.NotFound})
+>> Remove(t2, {Field1:99,Field2:{Field2_1:555,Field2_2:"2_2",Field2_3:{Field2_3_1:3231,Field2_3_2:"common"}}});
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-106: Missing column. Your formula is missing a column 'Field3' with a type of 'Boolean'.
>> Remove(t2, {Field2:{Field2_1:321,Field2_2:"2_2",Field2_3:{Field2_3_1:555,Field2_3_2:"common"}}});
-Error({Kind:ErrorKind.NotFound})
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-95: Missing column. Your formula is missing a column 'Field1' with a type of 'Decimal'.
>> Remove(t2, {Field2:{Field2_3:{Field2_3_2:"common"}}}, "All")
-Errors: Error 11-52: Invalid argument type. Expecting a Record value, but of a different schema.|Error 11-52: Missing column. Your formula is missing a column 'DisplayNameField2_1' with a type of 'Decimal'.|Error 0-6: The function 'Remove' has some invalid arguments.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-52: Missing column. Your formula is missing a column 'Field1' with a type of 'Decimal'.|Error 54-59: If provided, last argument must be 'RemoveFlags.All'. Is there a typo?
->> Remove(t2, {Field1:5}, "All")
+>> Remove(t2, {Field1:5}, RemoveFlags.All)
Error({Kind:ErrorKind.NotFound})
>> Remove(t2, {Field2:{Field2_1:321}});t2
-Errors: Error 11-34: Invalid argument type. Expecting a Record value, but of a different schema.|Error 11-34: Missing column. Your formula is missing a column 'DisplayNameField2_2' with a type of 'Text'.|Error 0-6: The function 'Remove' has some invalid arguments.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-34: Missing column. Your formula is missing a column 'Field1' with a type of 'Decimal'.
>> Remove(t2, {Field2:{Field2_3:{Field2_3_1:3231}}});t2
-Errors: Error 11-48: Invalid argument type. Expecting a Record value, but of a different schema.|Error 11-48: Missing column. Your formula is missing a column 'DisplayNameField2_1' with a type of 'Decimal'.|Error 0-6: The function 'Remove' has some invalid arguments.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-48: Missing column. Your formula is missing a column 'Field1' with a type of 'Decimal'.
->> Remove(t2, {Field2:{Field2_1:321,Field2_2:"2_2",Field2_3:{Field2_3_1:5555,Field2_3_2:"common"}}}, "All");t2
+>> Remove(t2, {Field2:{Field2_1:321,Field2_2:"2_2",Field2_3:{Field2_3_1:5555,Field2_3_2:"common"}}}, RemoveFlags.All);t2
Error({Kind:ErrorKind.NotFound})
// Wrong arguments
@@ -101,10 +95,10 @@ Errors: Error 14-18: If provided, last argument must be 'RemoveFlags.All'. Is th
Errors: Error 14-16: If provided, last argument must be 'RemoveFlags.All'. Is there a typo?|Error 0-6: The function 'Remove' has some invalid arguments.
>> Remove(t1, r1, r1, r1, r1, r1, r1, "Al");
-Errors: Error 35-39: If provided, last argument must be 'RemoveFlags.All'. Is there a typo?|Error 0-6: The function 'Remove' has some invalid arguments.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 35-39: If provided, last argument must be 'RemoveFlags.All'. Is there a typo?
>> Remove(t1, "All");
-Errors: Error 11-16: Invalid argument type (Text). Expecting a Record value instead.|Error 11-16: Cannot use a non-record value in this context.|Error 0-6: The function 'Remove' has some invalid arguments.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-16: Cannot use a non-record value in this context: '"All"'.
>> Collect(t1, r2);
Collect(t1, {Field1:3,Field2:"earth",Field3:DateTime(2030,2,1,0,0,0,0),Field4:true});
@@ -127,8 +121,8 @@ Table({Field1:1,Field2:"earth",Field3:DateTime(2022,1,1,0,0,0,0),Field4:true},{F
t1
Table({Field1:2,Field2:"moon",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false},{Field1:3,Field2:"earth",Field3:DateTime(2030,2,1,0,0,0,0),Field4:true},{Field1:4,Field2:"earth",Field3:DateTime(2040,2,1,0,0,0,0),Field4:false})
->> Remove(Foo, {Field1:5}, "All")
-Errors: Error 7-10: Name isn't valid. 'Foo' isn't recognized.|Error 12-22: The specified column 'Field1' does not exist.|Error 0-6: The function 'Remove' has some invalid arguments.
+>> Remove(Foo, {Field1:5}, RemoveFlags.All)
+Errors: Errors: Error 7-10: Name isn't valid. 'Foo' isn't recognized.
>> Remove(Foo, Bar)
Errors: Error 7-10: Name isn't valid. 'Foo' isn't recognized.|Error 12-15: Name isn't valid. 'Bar' isn't recognized.|Error 0-6: The function 'Remove' has some invalid arguments.
@@ -136,6 +130,9 @@ Errors: Error 7-10: Name isn't valid. 'Foo' isn't recognized.|Error 12-15: Name
>> Remove(t1, {Field1:2,Field2:"not in the table",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false})
Error({Kind:ErrorKind.NotFound})
-// Remove propagates error.
>> Remove(t1, If(1/0<2, {Field1:2}))
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-32: Missing column. Your formula is missing a column 'Field2' with a type of 'Text'.
+
+// Remove propagates error.
+>> Remove(t1, If(1/0<2, {Field1:1,Field2:"earth",Field3:DateTime(2022,1,1,0,0,0,0),Field4:true}))
Error({Kind:ErrorKind.Div0})
\ No newline at end of file
diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove_V1Compact.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove_V1Compact.txt
index b9a95d1f57..c4fec9328d 100644
--- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove_V1Compact.txt
+++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove_V1Compact.txt
@@ -1,9 +1,9 @@
#SETUP: PowerFxV1CompatibilityRules
-#SETUP: EnableExpressionChaining,MutationFunctionsTestSetup
+#SETUP: EnableExpressionChaining,MutationFunctionsTestSetup,StronglyTypedBuiltinEnums
// Check MutationFunctionsTestSetup handler (PowerFxEvaluationTests.cs) for documentation.
->> Remove(t2, {Field1:1,Field2:{Field2_1:121,Field2_2:"2_2",Field2_3:{Field2_3_1:1231,Field2_3_2:"common"}},Field3:false}, "All")
+>> Remove(t2, {Field1:1,Field2:{Field2_1:121,Field2_2:"2_2",Field2_3:{Field2_3_1:1231,Field2_3_2:"common"}},Field3:false}, RemoveFlags.All)
If(true, {test:1}, "Void value (result of the expression can't be used).")
>> Collect(t1, {Field1:2,Field2:"moon",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false});
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/AndOr_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/AndOr_V1Compat.txt
index b7ac223105..fafc0746e7 100644
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/AndOr_V1Compat.txt
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/AndOr_V1Compat.txt
@@ -1,4 +1,4 @@
-#SETUP: PowerFxV1CompatibilityRules
+#SETUP: PowerFxV1CompatibilityRules,StronglyTypedBuiltinEnums
// Case to test how shortcut verification work along with behavior functions
@@ -98,7 +98,7 @@ true
Table({Value:false},{Value:true})
// replaced with if from _V1CompatDisabled since short circuiting not supported with Void
->> If( !true, Remove(t1, First(t1), "All")); t1 // !true || operator
+>> If( !true, Remove(t1, First(t1), RemoveFlags.All)); t1 // !true || operator
Table({Value:false},{Value:true})
>> -3;t1
@@ -126,7 +126,7 @@ true
Table({Value:true},{Value:true},{Value:true})
// replaced with if from _V1CompatDisabled since short circuiting not supported with Void
->> If( !false, Remove(t1, First(t1), "All")); t1 // || Operator
+>> If( !false, Remove(t1, First(t1), RemoveFlags.All)); t1 // || Operator
Table()
>> 3;t1
@@ -152,5 +152,5 @@ true
Table({Value:true},{Value:true},{Value:true})
// replaced with if from _V1CompatDisabled since short circuiting not supported with Void
->> If( !false, Remove(t1, First(t1), "All")); t1 // Or Function
+>> If( !false, Remove(t1, First(t1), RemoveFlags.All)); t1 // Or Function
Table()
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove.txt
similarity index 100%
rename from src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1Compat.txt
rename to src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove.txt
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1CompatDisabled.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1CompatDisabled.txt
deleted file mode 100644
index 5d8da13dc6..0000000000
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1CompatDisabled.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-#SETUP: disable:PowerFxV1CompatibilityRules
-
->> Set(list, Table({ Name: "One", ID: 1}, { Name: "Two", ID: 2}))
-Table({ID:1,Name:"One"},{ID:2,Name:"Two"})
-
->> Set(list2, Table(First(list)))
-Table({ID:1,Name:"One"})
-
->> ForAll(list2, Remove(list, ThisRecord))
-Table(Blank())
-
->> list
-Table({ID:2,Name:"Two"})
-
->> Set(list3, [1,2,3,4])
-Table({Value:1},{Value:2},{Value:3},{Value:4})
-
->> Remove(list3, {Value:3})
-Blank()
-
->> Remove(list3, {Value:5})
-Error({Kind:ErrorKind.NotFound})
\ No newline at end of file
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs
index 705ca728b7..fd37de852e 100644
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs
@@ -164,7 +164,7 @@ public void DataTableEvalTest2()
var result7 = engine.Eval("Patch(robintable, First(robintable),{Names:\"new-name\"});robintable", options: opt);
Assert.Equal("Table({Names:\"new-name\",Scores:10},{Names:\"name3\",Scores:30},{Names:\"name100\",Scores:10})", ((DataTableValue)result7).Dump());
- var result8 = engine.Eval("Remove(robintable, {Scores:10}, \"All\");robintable", options: opt);
+ var result8 = engine.Eval("Remove(robintable, {Scores:10}, RemoveFlags.All);robintable", options: opt);
Assert.IsType(result8);
Assert.Equal(3, table.Rows.Count);