diff --git a/src/libraries/Microsoft.PowerFx.Core/App/IExternalEnabledFeatures.cs b/src/libraries/Microsoft.PowerFx.Core/App/IExternalEnabledFeatures.cs
index 52cd752720..8599f838b7 100644
--- a/src/libraries/Microsoft.PowerFx.Core/App/IExternalEnabledFeatures.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/App/IExternalEnabledFeatures.cs
@@ -36,6 +36,6 @@ internal sealed class DefaultEnabledFeatures : IExternalEnabledFeatures
public bool IsEnhancedComponentFunctionPropertyEnabled => true;
- public bool IsComponentFunctionPropertyDataflowEnabled => true;
+ public bool IsComponentFunctionPropertyDataflowEnabled => true;
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs
index 5778346a41..9abf51f816 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Functions/TexlFunction.cs
@@ -1167,7 +1167,7 @@ private bool SetErrorForMismatchedColumnsCore(DType expectedType, DType actualTy
}
// Second, set column missing message if applicable
- if (RequireAllParamColumns && !expectedType.AreFieldsOptional)
+ if ((RequireAllParamColumns || features.PowerFxV1CompatibilityRules) && !expectedType.AreFieldsOptional)
{
errors.EnsureError(
DocumentErrorSeverity.Severe,
diff --git a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs
index 0bb73cce3a..b5ee24186a 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);
@@ -655,6 +657,7 @@ internal static class TexlStrings
public static ErrorResourceKey ErrBadType_ExpectedType_ProvidedType = new ErrorResourceKey("ErrBadType_ExpectedType_ProvidedType");
public static ErrorResourceKey ErrBadType_VoidExpression = new ErrorResourceKey("ErrBadType_VoidExpression");
public static ErrorResourceKey ErrBadSchema_ExpectedType = new ErrorResourceKey("ErrBadSchema_ExpectedType");
+ public static ErrorResourceKey ErrNeedTable_Arg = new ErrorResourceKey("ErrNeedTable_Arg");
public static ErrorResourceKey ErrInvalidArgs_Func = new ErrorResourceKey("ErrInvalidArgs_Func");
public static ErrorResourceKey ErrNeedTable_Func = new ErrorResourceKey("ErrNeedTable_Func");
public static ErrorResourceKey ErrNeedTableCol_Func = new ErrorResourceKey("ErrNeedTableCol_Func");
@@ -868,5 +871,9 @@ internal static class TexlStrings
public static ErrorResourceKey ErrJoinArgIsNotAsNode = new ErrorResourceKey("ErrJoinArgIsNotAsNode");
public static ErrorResourceKey ErrJoinAtLeastOneRigthRecordField = new ErrorResourceKey("ErrJoinAtLeastOneRigthRecordField");
public static ErrorResourceKey ErrJoinDottedNameleft = new ErrorResourceKey("ErrJoinDottedNameleft");
+
+ 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 abf908f462..7d7e92e3d7 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs
@@ -82,6 +82,11 @@ public sealed class Features
///
internal bool IsUserDefinedTypesEnabled { get; init; } = false;
+ ///
+ /// Enables RemoveAll delegation.
+ ///
+ internal bool IsRemoveAllDelegationEnabled { get; init; }
+
internal static readonly Features None = new Features();
///
@@ -124,6 +129,7 @@ internal Features(Features other)
AsTypeLegacyCheck = other.AsTypeLegacyCheck;
JsonFunctionAcceptsLazyTypes = other.JsonFunctionAcceptsLazyTypes;
IsLookUpReductionDelegationEnabled = other.IsLookUpReductionDelegationEnabled;
+ IsRemoveAllDelegationEnabled = other.IsRemoveAllDelegationEnabled;
}
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs
index fee7d280c7..f5ffc29e48 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Values/CollectionTableValue.cs
@@ -184,7 +184,7 @@ public override DValue Last(bool mutationCopy = false)
public override async Task> RemoveAsync(IEnumerable recordsToRemove, bool all, CancellationToken cancellationToken)
{
var ret = false;
- var deleteList = new List();
+ var markedToDeletionIndexes = new HashSet();
var errors = new List();
cancellationToken.ThrowIfCancellationRequested();
@@ -194,21 +194,30 @@ public override async Task> RemoveAsync(IEnumerable dRecord = Marshal(item);
if (await MatchesAsync(dRecord.Value, recordToRemove, cancellationToken).ConfigureAwait(false))
{
- found = true;
-
- deleteList.Add(item);
+ if (markedToDeletionIndexes.Contains(i))
+ {
+ continue;
+ }
+ else
+ {
+ found = true;
+ markedToDeletionIndexes.Add(i);
+ }
if (!all)
{
@@ -220,13 +229,13 @@ public override async Task> RemoveAsync(IEnumerable 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;
+
+ public override bool MutatesArg(int argIndex, TexlNode arg) => argIndex == 0;
+
+ public override bool TryGetTypeForArgSuggestionAt(int argIndex, out DType type)
+ {
+ if (argIndex > 0)
+ {
+ type = default;
+ return false;
+ }
+
+ return base.TryGetTypeForArgSuggestionAt(argIndex, out type);
+ }
+
+ public RemoveBaseFunction(int arityMax, params DType[] paramTypes)
+ : base("Remove", TexlStrings.AboutRemove, FunctionCategories.Behavior, DType.EmptyTable, 0, 2, arityMax, paramTypes)
+ {
+ }
+
+ 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 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 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);
+ }
+
+ public bool CheckEnumType(Features features, DType argType)
+ {
+ var enumValid = BuiltInEnums.RemoveFlagsEnum.FormulaType._type.Accepts(argType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: features.PowerFxV1CompatibilityRules);
+
+ return (features.StronglyTypedBuiltinEnums && enumValid) ||
+ (!features.StronglyTypedBuiltinEnums && (DType.String.Accepts(argType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: features.PowerFxV1CompatibilityRules) || enumValid));
+ }
+ }
+
+ // Remove(collection:*[], item1:![], item2:![], ..., ["All"])
+ internal class RemoveFunction : RemoveBaseFunction, ISuggestionAwareFunction
+ {
+ public bool CanSuggestThisItem => true;
+
+ // Return true if this function affects datasource query options.
+ public override bool AffectsDataSourceQueryOptions => true;
+
+ public override bool ArgMatchesDatasourceType(int argNum)
+ {
+ return argNum >= 1;
+ }
+
+ 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);
+ }
+
+ public override IEnumerable GetRequiredEnumNames()
+ {
+ return new List() { LanguageConstants.RemoveFlagsEnumString };
+ }
+
+ 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);
+ }
+
+ int argCount = argTypes.Length;
+ for (int i = 1; i < argCount; i++)
+ {
+ DType argType = argTypes[i];
+
+ if (!argType.IsRecord)
+ {
+ if (argCount >= 3 && i == argCount - 1 && CheckEnumType(context.Features, argType))
+ {
+ continue;
+ }
+
+ 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.
+ // The item schema should be compatible with the collection schema.
+ if ((context.Features.PowerFxV1CompatibilityRules && (!collectionAcceptsRecord || !recordAcceptsCollection)) ||
+ (!context.Features.PowerFxV1CompatibilityRules && (!collectionAcceptsRecord && !recordAcceptsCollection)))
+ {
+ fValid = false;
+ if (!SetErrorForMismatchedColumns(collectionType, argType, args[i], errors, context.Features))
+ {
+ errors.EnsureError(DocumentErrorSeverity.Severe, args[i], TexlStrings.ErrTableDoesNotAcceptThisType);
+ }
+ }
+
+ // 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;
+ }
+
+ // 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 entirity 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;
+ }
+
+ 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 : RemoveBaseFunction
+ {
+ public override bool ArgMatchesDatasourceType(int argNum)
+ {
+ return argNum == 1;
+ }
+
+ public RemoveAllFunction()
+ : base(3, DType.EmptyTable, DType.EmptyTable)
+ {
+ }
+
+ public override IEnumerable GetSignatures()
+ {
+ yield return new[] { TexlStrings.RemoveArg1, TexlStrings.RemoveAllArg2 };
+ yield return new[] { TexlStrings.RemoveArg1, TexlStrings.RemoveAllArg2, TexlStrings.RemoveArg3 };
+ }
+
+ public override IEnumerable GetRequiredEnumNames()
+ {
+ return new List() { LanguageConstants.RemoveFlagsEnumString };
+ }
+
+ 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.ErrNeedTable_Func, Name);
+ }
+
+ // The source to be collected must be a table.
+ DType sourceType = argTypes[1];
+ if (!sourceType.IsTable)
+ {
+ fValid = false;
+ errors.EnsureError(args[1], TexlStrings.ErrNeedTable_Arg, args[1]);
+ }
+
+ // The source schema should be compatible with the collection schema.
+ if (!collectionType.Accepts(sourceType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules) && !sourceType.Accepts(collectionType, exact: true, useLegacyDateTimeAccepts: false, usePowerFxV1CompatibilityRules: context.Features.PowerFxV1CompatibilityRules))
+ {
+ fValid = false;
+ if (!SetErrorForMismatchedColumns(collectionType, sourceType, args[1], errors, context.Features))
+ {
+ errors.EnsureError(DocumentErrorSeverity.Severe, args[1], TexlStrings.ErrCollectionDoesNotAcceptThisType);
+ }
+ }
+
+ if (args.Length == 3 && !CheckEnumType(context.Features, argTypes[2]))
+ {
+ fValid = false;
+ errors.EnsureError(DocumentErrorSeverity.Severe, args[2], TexlStrings.ErrRemoveAllArg);
+ }
+
+ returnType = context.Features.PowerFxV1CompatibilityRules ? DType.Void : collectionType;
+
+ return fValid;
+ }
+
+ // 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.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 e560eb2c06..ddbfc8f736 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Types/Enums/BuiltInEnums.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Types/Enums/BuiltInEnums.cs
@@ -210,5 +210,14 @@ private static Dictionary TraceSeverityDictionary()
{ "Right", "right" },
{ "Full", "full" },
});
+
+ public static readonly EnumSymbol RemoveFlagsEnum = new EnumSymbol(
+ new DName(LanguageConstants.RemoveFlagsEnumString),
+ DType.String,
+ new Dictionary()
+ {
+ { "First", "first" },
+ { "All", "all" },
+ });
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs b/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs
index 435a06a989..8d1159fed1 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Types/Enums/EnumStoreBuilder.cs
@@ -31,6 +31,7 @@ internal sealed class EnumStoreBuilder
{ LanguageConstants.TraceSeverityEnumString, BuiltInEnums.TraceSeverityEnum },
{ LanguageConstants.TraceOptionsEnumString, BuiltInEnums.TraceOptionsEnum },
{ LanguageConstants.JoinTypeEnumString, BuiltInEnums.JoinTypeEnum },
+ { LanguageConstants.RemoveFlagsEnumString, BuiltInEnums.RemoveFlagsEnum },
};
// DefaultEnums, with enum strings, is legacy and only used by Power Apps
@@ -84,6 +85,10 @@ internal sealed class EnumStoreBuilder
{
LanguageConstants.JoinTypeEnumString,
$"%s[{string.Join(", ", BuiltInEnums.JoinTypeEnum.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 ca301dc4f8..ea341c9f2e 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Utils/LanguageConstants.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Utils/LanguageConstants.cs
@@ -90,5 +90,10 @@ internal class LanguageConstants
/// The string value representing the join type literal.
///
public const string JoinTypeEnumString = "JoinType";
+
+ ///
+ /// 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 78be4b4db7..d8dd875a99 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 afe2e61a37..0848094203 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryMutation.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryMutation.cs
@@ -5,7 +5,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.IR;
using Microsoft.PowerFx.Functions;
using Microsoft.PowerFx.Interpreter.Localization;
@@ -423,4 +422,22 @@ public async Task InvokeAsync(FunctionInvokeInfo invokeInfo, Cance
return await new ClearCollectImpl().InvokeAsync(invokeInfo, cancellationToken).ConfigureAwait(false);
}
}
+
+ // Remove(collection:*[], item1:![], item2:![], ..., ["All"])
+ internal class RemoveImpl : RemoveFunction, IFunctionInvoker
+ {
+ public async Task InvokeAsync(FunctionInvokeInfo invokeInfo, CancellationToken cancellationToken)
+ {
+ return await MutationUtils.RemoveCore(invokeInfo, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ // Remove(collection:*[], source:*[], ["All"])
+ internal class RemoveAllImpl : RemoveAllFunction, IFunctionInvoker
+ {
+ public async Task InvokeAsync(FunctionInvokeInfo invokeInfo, CancellationToken cancellationToken)
+ {
+ return await MutationUtils.RemoveCore(invokeInfo, 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..4d06427f68 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.Functions;
+using Microsoft.PowerFx.Interpreter.Localization;
using Microsoft.PowerFx.Types;
namespace Microsoft.PowerFx.Interpreter
@@ -47,5 +45,90 @@ 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(FunctionInvokeInfo invokeInfo, CancellationToken cancellationToken)
+ {
+ var args = invokeInfo.Args.ToArray();
+ 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.FirstOrDefault(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(invokeInfo.IRContext, errors);
+ }
+
+ return invokeInfo.ReturnType == 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 7ce73eeba3..0000000000
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Mutation/RemoveFunction.cs
+++ /dev/null
@@ -1,242 +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(IReadOnlyList 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, IFunctionInvoker
- {
- 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(FunctionInvokeInfo invokeInfo, CancellationToken cancellationToken)
- {
- var args = invokeInfo.Args;
- var returnType = invokeInfo.ReturnType;
-
- 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.Count - 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, returnType == FormulaType.Void ? FormulaType.Void : FormulaType.Blank);
- }
- else
- {
- result = returnType == 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 bd38c3afe3..21957d5b81 100644
--- a/src/strings/PowerFxResources.en-US.resx
+++ b/src/strings/PowerFxResources.en-US.resx
@@ -3291,21 +3291,41 @@
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.
-
- 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).
+
+ item
+ function_parameter - Second parameter to the Remove function - the item to be removed.
If provided, last argument must be 'RemoveFlags.All'. Is there a typo?
{Locked=RemoveFlags.All} Error Message, RemoveFlags.All is an enum value that does not get localized.
+
+ The collection to remove rows from.
+
+
+ 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.
+
Error
Display text representing the Error value of NotificationType enum (NotificationType_Error_Name). This describes showing an error notification. The possible values for this enumeration are: Error, Warning, Success, Information.
@@ -4922,4 +4942,60 @@
The '{0}' value is invalid in this context. It should be a reference to either '{1}' or '{2}' scope name.
{Locked='RightRecord'}
+
+ The first argument of '{0}' should be a collection.
+ Error Message.
+
+
+ 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..df1ce9d7f4 100644
--- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt
+++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/Remove.txt
@@ -1,110 +1,19 @@
-#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
-Table({Field1:2,Field2:"moon",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false})
-
->> 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});
- t1
-Error({Kind:ErrorKind.NotFound})
-
->> 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");
- 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");
- 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");
- t1
-Error(Table({Kind:ErrorKind.NotFound},{Kind:ErrorKind.NotFound}))
-
->> 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, {DisplayNameField1:3,DisplayNameField2:"earth",DisplayNameField3:DateTime(2022,2,1,0,0,0,0),DisplayNameField4:false});
- 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},{Field1:4,Field2:"earth",Field3:DateTime(2022,2,1,0,0,0,0),Field4:false})
-
->> 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");
- 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})
-
->> Remove(t2, {Field1:1,Field2:{Field2_1:121,Field2_2:"2_2",Field2_3:{Field2_3_1:1231,Field2_3_2:"common"}},Field3:false}); t2
-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.
-
->> 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.
-
->> 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})
-
->> Remove(t2, {Field1:5})
-Error({Kind:ErrorKind.NotFound})
-
->> 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, {Field2:{Field2_1:321,Field2_2:"2_2",Field2_3:{Field2_3_1:555,Field2_3_2:"common"}}});
-Error({Kind:ErrorKind.NotFound})
-
-
->> 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.
-
->> Remove(t2, {Field1:5}, "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.
-
->> 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:5555,Field2_3_2:"common"}}}, "All");t2
-Error({Kind:ErrorKind.NotFound})
-
// Wrong arguments
>> Remove(t1, r1,"Al");
-Errors: Error 14-18: 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 14-18: Cannot use a non-record value in this context: '"Al"'.
>> Remove(t1, r1,"");
-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.
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 14-16: Cannot use a non-record value in this context: '""'.
>> 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: Cannot use a non-record value in this context: '"Al"'.
>> 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,15 +36,9 @@ 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: 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.
->> 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}))
-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..c9c871faa2 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,8 @@
-#SETUP: PowerFxV1CompatibilityRules
-#SETUP: EnableExpressionChaining,MutationFunctionsTestSetup
+#SETUP: PowerFxV1CompatibilityRules,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.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove.txt
new file mode 100644
index 0000000000..7d7965b101
--- /dev/null
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove.txt
@@ -0,0 +1,108 @@
+#SETUP: PowerFxV1CompatibilityRules,StronglyTypedBuiltinEnums
+
+>> Set(t1, Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)}))
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Set(t2, t1)
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t1, First(t1))
+If(true, {test:1}, "Void value (result of the expression can't be used).")
+
+>> 0;t1
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> 0;Set(t1, t2)
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t1, First(t1), RemoveFlags.All)
+If(true, {test:1}, "Void value (result of the expression can't be used).")
+
+>> 1;t1
+Table({a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> 1;Set(t1, t2)
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t1, {a:true})
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-19: Missing column. Your formula is missing a column 'b' with a type of 'Text'.
+
+>> Remove(t1, {a:true,b:true})
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-26: Incompatible type. The 'b' column in the data source you’re updating expects a 'Text' type and you’re using a 'Boolean' type.
+
+>> 2;t1
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t1, {a:true,b:"does not exist",c:Now()})
+Error({Kind:ErrorKind.NotFound})
+
+>> Remove(t1, {a:true,b:"does not exist",c:Now()}, RemoveFlags.All)
+Error({Kind:ErrorKind.NotFound})
+
+>> Remove(t1, {a:true,b:"does not exist",c:Now()}, {a:false,b:"does not exist",c:Now()}, RemoveFlags.All)
+Error(Table({Kind:ErrorKind.NotFound},{Kind:ErrorKind.NotFound}))
+
+>> 3;t1
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t1, If(1/0<2, {a:true,b:"hello"}))
+Errors: Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-40: Missing column. Your formula is missing a column 'c' with a type of 'DateTime'.
+
+>> 4;t1
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Set(t3, t1)
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+// Removing multiple rows with the same values.
+>> Remove(t3, {a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)}, {a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)}, {a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)})
+If(true, {test:1}, "Void value (result of the expression can't be used).")
+
+>> 0;t3
+Table({a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> 0;Set(t3, t1)
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t3, {a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)}, {a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)}, {a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)}, RemoveFlags.All)
+Error(Table({Kind:ErrorKind.NotFound},{Kind:ErrorKind.NotFound}))
+
+>> 1;t3
+Table({a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> 1;Set(t3, t1)
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t3, t3)
+If(true, {test:1}, "Void value (result of the expression can't be used).")
+
+>> 2;t3
+Table()
+
+>> 2;Set(t3, t1)
+Table({a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:true,b:"hi",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hello",c:DateTime(2024,1,1,0,0,0,0)},{a:false,b:"hi",c:DateTime(2024,1,1,0,0,0,0)})
+
+>> Remove(t3, t3, RemoveFlags.All)
+Error(Table({Kind:ErrorKind.NotFound},{Kind:ErrorKind.NotFound}))
+
+// Remove propagates error.
+>> Remove(t1, If(1/0<2, {a:true,b:"hello",c:DateTime(2024,1,1,0,0,0,0)}))
+Error({Kind:ErrorKind.Div0})
+
+>> Set(t4, Table({a:{aa:{aaa:true,bbb:true}}}))
+Table({a:{aa:{aaa:true,bbb:true}}})
+
+>> Remove(t4, {a:{aa:{aaa:true}}})
+Errors: Error 0-6: The function 'Remove' has some invalid arguments.|Error 11-30: Missing column. Your formula is missing a column 'a.aa.bbb' with a type of 'Boolean'.
+
+>> Remove(t4, {a:{aa:{aaa:true,bbb:false}}})
+Error({Kind:ErrorKind.NotFound})
+
+>> Remove(t4, {a:{aa:{aaa:true,bbb:false}}}, RemoveFlags.All)
+Error({Kind:ErrorKind.NotFound})
+
+>> Remove(t4, {a:{aa:{aaa:true,bbb:true}}})
+If(true, {test:1}, "Void value (result of the expression can't be used).")
+
+>> t4
+Table()
\ No newline at end of file
diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1Compat.txt b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1Compat.txt
deleted file mode 100644
index 761f0ce599..0000000000
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/MutationScripts/Remove_V1Compat.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-#SETUP: 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))
-If(true, {test:1}, "Void value (result of the expression can't be used).")
-
->> 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})
-If(true, {test:1}, "Void value (result of the expression can't be used).")
-
->> Remove(list3, {Value:5})
-Error({Kind:ErrorKind.NotFound})
\ No newline at end of file
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..1dd46080a3 100644
--- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs
+++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/PADIntegrationTests.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Data;
+using System.Linq;
using Microsoft.PowerFx.Interpreter.Tests;
using Microsoft.PowerFx.Types;
using Xunit;
@@ -164,8 +165,8 @@ 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);
- Assert.IsType(result8);
+ var check = engine.Check("Remove(robintable, {Scores:10}, RemoveFlags.All)", options: opt);
+ Assert.False(check.IsSuccess);
Assert.Equal(3, table.Rows.Count);
}