diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ExternalCdpDataSource.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ExternalCdpDataSource.cs
index b213734b68..5badccba7c 100644
--- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ExternalCdpDataSource.cs
+++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ExternalCdpDataSource.cs
@@ -36,6 +36,8 @@ public ExternalCdpDataSource(DName name, string datasetName, ServiceCapabilities
public TabularDataQueryOptions QueryOptions => new TabularDataQueryOptions(this);
+ public bool HasCachedCountRows => false;
+
public string Name => EntityName.Value;
public bool IsSelectable => ServiceCapabilities.IsSelectable;
diff --git a/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalTabularDataSource.cs b/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalTabularDataSource.cs
index 1c30c12158..7d69e78e0c 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalTabularDataSource.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalTabularDataSource.cs
@@ -11,6 +11,13 @@ internal interface IExternalTabularDataSource : IExternalDataSource, IDisplayMap
{
TabularDataQueryOptions QueryOptions { get; }
+ ///
+ /// Some data sources (like Dataverse) may return a cached value for
+ /// the number of rows (calls to CountRows) instead of always retrieving
+ /// the latest count.
+ ///
+ bool HasCachedCountRows { get; }
+
IReadOnlyList GetKeyColumns();
IEnumerable GetKeyColumns(IExpandInfo expandInfo);
@@ -23,4 +30,4 @@ internal interface IExternalTabularDataSource : IExternalDataSource, IDisplayMap
bool CanIncludeExpand(IExpandInfo parentExpandInfo, IExpandInfo expandToAdd);
}
-}
\ No newline at end of file
+}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs b/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs
index fb6a05e1b5..e59d6713ef 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Lexer/TexlLexer.cs
@@ -81,7 +81,8 @@ public enum Flags
public const string PunctuatorColon = ":";
public const string PunctuatorAt = "@";
public const char IdentifierDelimiter = '\'';
- public const string PunctuatorDoubleBarrelArrow = "=>";
+ public const string PunctuatorDoubleBarrelArrow = "=>";
+ public const string PunctuatorColonEqual = ":=";
// These puntuators are related to commenting in the formula bar
public const string PunctuatorBlockComment = "/*";
@@ -305,7 +306,8 @@ private TexlLexer(string preferredDecimalSeparator)
AddPunctuator(punctuators, PunctuatorAmpersand, TokKind.Ampersand);
AddPunctuator(punctuators, PunctuatorPercent, TokKind.PercentSign);
AddPunctuator(punctuators, PunctuatorAt, TokKind.At);
- AddPunctuator(punctuators, PunctuatorDoubleBarrelArrow, TokKind.DoubleBarrelArrow);
+ AddPunctuator(punctuators, PunctuatorDoubleBarrelArrow, TokKind.DoubleBarrelArrow);
+ AddPunctuator(punctuators, PunctuatorColonEqual, TokKind.ColonEqual);
// Commenting punctuators
AddPunctuator(punctuators, PunctuatorBlockComment, TokKind.Comment);
diff --git a/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs b/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs
index a91473efe9..55ef86bd0b 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Lexer/TokKind.cs
@@ -303,6 +303,12 @@ public enum TokKind
/// Start of body for user defined functions.
/// =>
///
- DoubleBarrelArrow,
+ DoubleBarrelArrow,
+
+ ///
+ /// Colon immediately followed by equal.
+ /// :=
+ ///
+ ColonEqual,
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs
index 14185fe5cd..683699dfcc 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs
@@ -750,6 +750,8 @@ internal static class TexlStrings
public static ErrorResourceKey WarnDeferredType = new ErrorResourceKey("WarnDeferredType");
public static ErrorResourceKey ErrColRenamedTwice_Name = new ErrorResourceKey("ErrColRenamedTwice_Name");
+ public static ErrorResourceKey WrnCountRowsMayReturnCachedValue = new ErrorResourceKey("WrnCountRowsMayReturnCachedValue");
+
public static StringGetter InfoMessage = (b) => StringResources.Get("InfoMessage", b);
public static StringGetter InfoNode_Node = (b) => StringResources.Get("InfoNode_Node", b);
public static StringGetter InfoTok_Tok = (b) => StringResources.Get("InfoTok_Tok", b);
@@ -766,6 +768,7 @@ internal static class TexlStrings
public static ErrorResourceKey ErrNamedFormula_MissingSemicolon = new ErrorResourceKey("ErrNamedFormula_MissingSemicolon");
public static ErrorResourceKey ErrNamedFormula_MissingValue = new ErrorResourceKey("ErrNamedFormula_MissingValue");
+ public static ErrorResourceKey ErrNamedType_MissingTypeLiteral = new ErrorResourceKey("ErrNamedType_MissingTypeLiteral");
public static ErrorResourceKey ErrUDF_MissingFunctionBody = new ErrorResourceKey("ErrUDF_MissingFunctionBody");
public static ErrorResourceKey ErrNamedFormula_AlreadyDefined = new ErrorResourceKey("ErrNamedFormula_AlreadyDefined");
public static ErrorResourceKey ErrorResource_NameConflict = new ErrorResourceKey("ErrorResource_NameConflict");
@@ -848,5 +851,7 @@ internal static class TexlStrings
public static ErrorResourceKey ErrInvalidDataSourceForFunction = new ErrorResourceKey("ErrInvalidDataSourceForFunction");
public static ErrorResourceKey ErrInvalidArgumentExpectedType = new ErrorResourceKey("ErrInvalidArgumentExpectedType");
public static ErrorResourceKey ErrUnsupportedTypeInTypeArgument = new ErrorResourceKey("ErrUnsupportedTypeInTypeArgument");
+ public static ErrorResourceKey ErrReachedMaxJsonDepth = new ErrorResourceKey("ErrReachedMaxJsonDepth");
+ public static ErrorResourceKey ErrReachedMaxJsonLength = new ErrorResourceKey("ErrReachedMaxJsonLength");
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs b/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs
index 8e642a5bd1..8f95c9a50f 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Parser/TexlParser.cs
@@ -300,7 +300,7 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse
continue;
}
- if (_curs.TidCur == TokKind.Equ)
+ if (_curs.TidCur == TokKind.ColonEqual && _flagsMode.Peek().HasFlag(Flags.AllowTypeLiteral))
{
var declaration = script.Substring(declarationStart, _curs.TokCur.Span.Min - declarationStart);
_curs.TokMove();
@@ -308,7 +308,7 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse
if (_curs.TidCur == TokKind.Semicolon)
{
- CreateError(thisIdentifier, TexlStrings.ErrNamedFormula_MissingValue);
+ CreateError(thisIdentifier, TexlStrings.ErrNamedType_MissingTypeLiteral);
}
// Extract expression
@@ -334,6 +334,49 @@ private ParseUserDefinitionResult ParseUDFsAndNamedFormulas(string script, Parse
definitionBeforeTrivia = new List();
continue;
}
+ else
+ {
+ CreateError(_curs.TokCur, TexlStrings.ErrNamedType_MissingTypeLiteral);
+ }
+
+ // If the result was an error, keep moving cursor until end of named type expression
+ if (result.Kind == NodeKind.Error)
+ {
+ while (_curs.TidCur != TokKind.Semicolon && _curs.TidCur != TokKind.Eof)
+ {
+ _curs.TokMove();
+ }
+ }
+ }
+
+ declarationStart = _curs.TokCur.Span.Lim;
+ _curs.TokMove();
+ ParseTrivia();
+ }
+ else if (_curs.TidCur == TokKind.Equ)
+ {
+ var declaration = script.Substring(declarationStart, _curs.TokCur.Span.Min - declarationStart);
+ _curs.TokMove();
+ definitionBeforeTrivia.Add(ParseTrivia());
+
+ if (_curs.TidCur == TokKind.Semicolon)
+ {
+ CreateError(thisIdentifier, TexlStrings.ErrNamedFormula_MissingValue);
+ }
+
+ // Extract expression
+ while (_curs.TidCur != TokKind.Semicolon)
+ {
+ // Check if we're at EOF before a semicolon is found
+ if (_curs.TidCur == TokKind.Eof)
+ {
+ CreateError(_curs.TokCur, TexlStrings.ErrNamedFormula_MissingSemicolon);
+ break;
+ }
+
+ // Parse expression
+ definitionBeforeTrivia.Add(ParseTrivia());
+ var result = ParseExpr(Precedence.None);
namedFormulas.Add(new NamedFormula(thisIdentifier.As(), new Formula(result.GetCompleteSpan().GetFragment(script), result), _startingIndex, attribute));
userDefinitionSourceInfos.Add(new UserDefinitionSourceInfo(index++, UserDefinitionType.NamedFormula, thisIdentifier.As(), declaration, new SourceList(definitionBeforeTrivia), GetExtraTriviaSourceList()));
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Canceller.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Canceller.cs
new file mode 100644
index 0000000000..ce55553618
--- /dev/null
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Canceller.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.PowerFx
+{
+ internal class Canceller
+ {
+ private readonly List _cancellationAction;
+
+ public Canceller()
+ {
+ _cancellationAction = new List();
+ }
+
+ public Canceller(params Action[] cancellationActions)
+ : this()
+ {
+ if (cancellationActions != null)
+ {
+ foreach (Action cancellationAction in cancellationActions)
+ {
+ AddAction(cancellationAction);
+ }
+ }
+ }
+
+ public void AddAction(Action cancellationAction)
+ {
+ if (cancellationAction != null)
+ {
+ _cancellationAction.Add(cancellationAction);
+ }
+ }
+
+ public void ThrowIfCancellationRequested()
+ {
+ foreach (Action cancellationAction in _cancellationAction)
+ {
+ cancellationAction();
+ }
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs
index 86b1d4e6ea..a8aa0d3ec9 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs
@@ -208,63 +208,39 @@ public void AddConstant(string name, FormulaValue data)
}
///
- /// Adds an user defined function.
+ /// Adds user defined functions in the script.
///
/// String representation of the user defined function.
/// CultureInfo to parse the script againts. Default is invariant.
/// Extra symbols to bind UDF. Commonly coming from Engine.
/// Additional symbols to bind UDF.
/// Allow for curly brace parsing.
- internal void AddUserDefinedFunction(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, ReadOnlySymbolTable extraSymbolTable = null, bool allowSideEffects = false)
+ internal DefinitionsCheckResult AddUserDefinedFunction(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, ReadOnlySymbolTable extraSymbolTable = null, bool allowSideEffects = false)
{
// Phase 1: Side affects are not allowed.
// Phase 2: Introduces side effects and parsing of function bodies.
var options = new ParserOptions()
{
AllowsSideEffects = allowSideEffects,
- Culture = parseCulture ?? CultureInfo.InvariantCulture
+ Culture = parseCulture ?? CultureInfo.InvariantCulture,
};
- var sb = new StringBuilder();
- var parseResult = UserDefinitions.Parse(script, options);
-
- // Compose will handle null symbols
var composedSymbols = Compose(this, symbolTable, extraSymbolTable);
- var udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs.Where(udf => udf.IsParseValid), composedSymbols, out var errors);
-
- errors.AddRange(parseResult.Errors ?? Enumerable.Empty());
+ var checkResult = new DefinitionsCheckResult();
- if (errors.Any(error => error.Severity > DocumentErrorSeverity.Warning))
- {
- sb.AppendLine("Something went wrong when parsing user defined functions.");
+ var udfs = checkResult.SetText(script, options)
+ .SetBindingInfo(composedSymbols)
+ .ApplyCreateUserDefinedFunctions();
- foreach (var error in errors)
- {
- error.FormatCore(sb);
- }
-
- throw new InvalidOperationException(sb.ToString());
- }
+ Contracts.AssertValue(udfs);
- foreach (var udf in udfs)
+ if (checkResult.IsSuccess)
{
- AddFunction(udf);
- var config = new BindingConfig(allowsSideEffects: allowSideEffects, useThisRecordForRuleScope: false, numberIsFloat: false);
- var binding = udf.BindBody(composedSymbols, new Glue2DocumentBinderGlue(), config);
-
- List bindErrors = new List();
-
- if (binding.ErrorContainer.GetErrors(ref bindErrors))
- {
- sb.AppendLine(string.Join(", ", errors.Select(err => err.ToString())));
- }
+ AddFunctions(udfs);
}
- if (sb.Length > 0)
- {
- throw new InvalidOperationException(sb.ToString());
- }
+ return checkResult;
}
///
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs b/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs
index f4f6bfa9ac..4dad15d5c5 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs
@@ -12,6 +12,9 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.PowerFx.Core.Binding;
using Microsoft.PowerFx.Core.Errors;
+using Microsoft.PowerFx.Core.Functions;
+using Microsoft.PowerFx.Core.Glue;
+using Microsoft.PowerFx.Core.Localization;
using Microsoft.PowerFx.Core.Parser;
using Microsoft.PowerFx.Core.Types;
using Microsoft.PowerFx.Core.Utils;
@@ -30,11 +33,16 @@ public class DefinitionsCheckResult : IOperationStatus
private IReadOnlyDictionary _resolvedTypes;
+ private TexlFunctionSet _userDefinedFunctions;
+
private CultureInfo _defaultErrorCulture;
private ParserOptions _parserOptions;
private ParseUserDefinitionResult _parse;
+ // Local symboltable to store new symbols in a given script and use in binding.
+ private readonly SymbolTable _localSymbolTable;
+
// Power Fx expression containing definitions
private string _definitions;
@@ -43,6 +51,7 @@ public class DefinitionsCheckResult : IOperationStatus
public DefinitionsCheckResult()
{
+ _localSymbolTable = new SymbolTable { DebugName = "LocalUserDefinitions" };
}
internal DefinitionsCheckResult SetBindingInfo(ReadOnlySymbolTable symbols)
@@ -59,7 +68,7 @@ internal DefinitionsCheckResult SetBindingInfo(ReadOnlySymbolTable symbols)
return this;
}
- internal DefinitionsCheckResult SetText(string definitions, ParserOptions parserOptions = null)
+ public DefinitionsCheckResult SetText(string definitions, ParserOptions parserOptions = null)
{
Contracts.AssertValue(definitions);
@@ -97,6 +106,8 @@ internal ParseUserDefinitionResult ApplyParse()
public IReadOnlyDictionary ResolvedTypes => _resolvedTypes;
+ public bool ContainsUDF => _parse.UDFs.Any();
+
internal IReadOnlyDictionary ApplyResolveTypes()
{
if (_parse == null)
@@ -114,6 +125,7 @@ internal IReadOnlyDictionary ApplyResolveTypes()
if (_parse.DefinedTypes.Any())
{
this._resolvedTypes = DefinedTypeResolver.ResolveTypes(_parse.DefinedTypes.Where(dt => dt.IsParseValid), _symbols, out var errors);
+ this._localSymbolTable.AddTypes(this._resolvedTypes);
_errors.AddRange(ExpressionError.New(errors, _defaultErrorCulture));
}
else
@@ -125,16 +137,79 @@ internal IReadOnlyDictionary ApplyResolveTypes()
return this._resolvedTypes;
}
+ internal TexlFunctionSet ApplyCreateUserDefinedFunctions()
+ {
+ if (_parse == null)
+ {
+ this.ApplyParse();
+ }
+
+ if (_symbols == null)
+ {
+ throw new InvalidOperationException($"Must call {nameof(SetBindingInfo)} before calling ApplyCreateUserDefinedFunctions().");
+ }
+
+ if (_resolvedTypes == null)
+ {
+ this.ApplyResolveTypes();
+ }
+
+ if (_userDefinedFunctions == null)
+ {
+ _userDefinedFunctions = new TexlFunctionSet();
+
+ var partialUDFs = UserDefinedFunction.CreateFunctions(_parse.UDFs.Where(udf => udf.IsParseValid), _symbols, out var errors);
+
+ if (errors.Any())
+ {
+ _errors.AddRange(ExpressionError.New(errors, _defaultErrorCulture));
+ }
+
+ var composedSymbols = ReadOnlySymbolTable.Compose(_localSymbolTable, _symbols);
+ foreach (var udf in partialUDFs)
+ {
+ var config = new BindingConfig(allowsSideEffects: _parserOptions.AllowsSideEffects, useThisRecordForRuleScope: false, numberIsFloat: false);
+ var binding = udf.BindBody(composedSymbols, new Glue2DocumentBinderGlue(), config);
+
+ List bindErrors = new List();
+
+ if (binding.ErrorContainer.HasErrors())
+ {
+ _errors.AddRange(ExpressionError.New(binding.ErrorContainer.GetErrors(), _defaultErrorCulture));
+ }
+ else
+ {
+ _localSymbolTable.AddFunction(udf);
+ _userDefinedFunctions.Add(udf);
+ }
+ }
+
+ return this._userDefinedFunctions;
+ }
+
+ return this._userDefinedFunctions;
+ }
+
internal IEnumerable ApplyErrors()
{
if (_resolvedTypes == null)
{
- ApplyResolveTypes();
+ this.ApplyCreateUserDefinedFunctions();
}
return this.Errors;
}
+ public IEnumerable ApplyParseErrors()
+ {
+ if (_parse == null)
+ {
+ this.ApplyParse();
+ }
+
+ return ExpressionError.New(_parse.Errors, _defaultErrorCulture);
+ }
+
///
/// List of all errors and warnings. Check .
/// This can include Parse, ResolveType errors />,
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs
index 869129ebc8..06152a8426 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Engine.cs
@@ -546,10 +546,10 @@ public string GetDisplayExpression(string expressionText, ReadOnlySymbolTable sy
return ExpressionLocalizationHelper.ConvertExpression(expressionText, ruleScope, GetDefaultBindingConfig(), CreateResolverInternal(symbolTable), CreateBinderGlue(), culture, Config.Features, toDisplay: true);
}
- internal void AddUserDefinedFunction(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, bool allowSideEffects = false)
+ public DefinitionsCheckResult AddUserDefinedFunction(string script, CultureInfo parseCulture = null, ReadOnlySymbolTable symbolTable = null, bool allowSideEffects = false)
{
var engineTypesAndFunctions = ReadOnlySymbolTable.Compose(PrimitiveTypes, SupportedFunctions);
- Config.SymbolTable.AddUserDefinedFunction(script, parseCulture, engineTypesAndFunctions, symbolTable, allowSideEffects);
+ return Config.SymbolTable.AddUserDefinedFunction(script, parseCulture, engineTypesAndFunctions, symbolTable, allowSideEffects);
}
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Values/NamedValue.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Values/NamedValue.cs
index f3a9d14ae5..9f62898a91 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Public/Values/NamedValue.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Public/Values/NamedValue.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerFx.Core.Types;
@@ -17,7 +18,17 @@ public class NamedValue
{
public string Name { get; }
- public FormulaValue Value => _value ?? _getFormulaValue().ConfigureAwait(false).GetAwaiter().GetResult();
+ public FormulaValue Value => ValueAsync().GetAwaiter().GetResult();
+
+ public async Task ValueAsync()
+ {
+ if (_value != null)
+ {
+ return _value;
+ }
+
+ return await _getFormulaValue().ConfigureAwait(false);
+ }
///
/// Useful for determining if the value is an entity or not.
diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountRows.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountRows.cs
index e04184c204..60caada7f3 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountRows.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/CountRows.cs
@@ -5,6 +5,7 @@
using System.Linq;
using Microsoft.PowerFx.Core.App.ErrorContainers;
using Microsoft.PowerFx.Core.Binding;
+using Microsoft.PowerFx.Core.Binding.BindInfo;
using Microsoft.PowerFx.Core.Entities;
using Microsoft.PowerFx.Core.Errors;
using Microsoft.PowerFx.Core.Functions;
@@ -68,6 +69,32 @@ public override bool IsServerDelegatable(CallNode callNode, TexlBinding binding)
return TryGetValidDataSourceForDelegation(callNode, binding, out var dataSource, out var preferredFunctionDelegationCapability);
}
+ public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
+ {
+ base.CheckSemantics(binding, args, argTypes, errors);
+ if (args[0] is not FirstNameNode node)
+ {
+ // No additional check
+ return;
+ }
+
+ var info = binding.GetInfo(args[0] as FirstNameNode);
+ if (info.Kind != BindKind.Data)
+ {
+ // No additional check
+ return;
+ }
+
+ if (argTypes[0].AssociatedDataSources?.Count == 1)
+ {
+ var associatedDataSource = argTypes[0].AssociatedDataSources.Single();
+ if (associatedDataSource.HasCachedCountRows)
+ {
+ errors.EnsureError(DocumentErrorSeverity.Warning, node, TexlStrings.WrnCountRowsMayReturnCachedValue);
+ }
+ }
+ }
+
// See if CountDistinct delegation is available. If true, we can make use of it on primary key as a workaround for CountRows delegation
internal bool TryGetValidDataSourceForDelegation(CallNode callNode, TexlBinding binding, out IExternalDataSource dataSource, out DelegationCapability preferredFunctionDelegationCapability)
{
diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs
index 75006f6005..b80f141819 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs
@@ -17,7 +17,7 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
{
// JSON(data:any, [format:s])
internal class JsonFunction : BuiltinFunction
- {
+ {
private const char _includeBinaryDataEnumValue = 'B';
private const char _ignoreBinaryDataEnumValue = 'G';
private const char _ignoreUnsupportedTypesEnumValue = 'I';
@@ -31,26 +31,25 @@ internal class JsonFunction : BuiltinFunction
DKind.DataEntity,
DKind.LazyRecord,
DKind.LazyTable,
- DKind.View,
+ DKind.View,
DKind.ViewValue
};
private static readonly DKind[] _unsupportedTypes = new[]
{
- DKind.Control,
+ DKind.Control,
DKind.LazyRecord,
DKind.LazyTable,
DKind.Metadata,
- DKind.OptionSet,
- DKind.PenImage,
+ DKind.OptionSet,
+ DKind.PenImage,
DKind.Polymorphic,
- DKind.UntypedObject,
DKind.Void
};
public override bool IsSelfContained => true;
- public override bool IsAsync => true;
+ public override bool IsAsync => true;
public override bool SupportsParamCoercion => false;
@@ -78,13 +77,13 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp
// Do not call base.CheckTypes for arg0
if (args.Length > 1)
{
- if (context.Features.StronglyTypedBuiltinEnums &&
+ if (context.Features.StronglyTypedBuiltinEnums &&
!base.CheckType(context, args[1], argTypes[1], BuiltInEnums.JSONFormatEnum.FormulaType._type, errors, ref nodeToCoercedTypeMap))
{
return false;
}
- TexlNode optionsNode = args[1];
+ TexlNode optionsNode = args[1];
if (!IsConstant(context, argTypes, optionsNode, out string nodeValue))
{
errors.EnsureError(optionsNode, TexlStrings.ErrFunctionArg2ParamMustBeConstant, "JSON", TexlStrings.JSONArg2.Invoke());
@@ -117,11 +116,11 @@ public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[
bool includeBinaryData = false;
bool ignoreUnsupportedTypes = false;
- bool ignoreBinaryData = false;
+ bool ignoreBinaryData = false;
if (args.Length > 1)
{
- TexlNode optionsNode = args[1];
+ TexlNode optionsNode = args[1];
if (!IsConstant(binding.CheckTypesContext, argTypes, optionsNode, out string nodeValue))
{
return;
@@ -180,12 +179,12 @@ public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[
}
if (!ignoreUnsupportedTypes)
- {
+ {
if (HasUnsupportedType(dataArgType, supportsLazyTypes, out DType unsupportedNestedType, out var unsupportedColumnName))
{
errors.EnsureError(dataNode, TexlStrings.ErrJSONArg1UnsupportedNestedType, unsupportedColumnName, unsupportedNestedType.GetKindString());
}
- }
+ }
}
private static bool IsConstant(CheckTypesContext context, DType[] argTypes, TexlNode optionsNode, out string nodeValue)
diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trace.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trace.cs
index 39d2d1904d..116bf89839 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trace.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Trace.cs
@@ -44,6 +44,13 @@ public override IEnumerable GetRequiredEnumNames()
yield return new[] { TexlStrings.TraceArg1, TexlStrings.TraceArg2, TexlStrings.TraceArg3, TexlStrings.TraceArg4 };
}
+ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary nodeToCoercedTypeMap)
+ {
+ var result = base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap);
+ returnType = context.Features.PowerFxV1CompatibilityRules ? DType.Void : DType.Boolean;
+ return result;
+ }
+
public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
{
Contracts.AssertValue(args);
diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Intellisense/Intellisense.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Intellisense/Intellisense.cs
index 58d9a85b0c..69c7f3e2dc 100644
--- a/src/libraries/Microsoft.PowerFx.Core/Texl/Intellisense/Intellisense.cs
+++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Intellisense/Intellisense.cs
@@ -187,13 +187,14 @@ protected static void TypeMatchPriority(DType type, IList Visit(CallNode node, EvalVisitorCo
}
else if (func is IAsyncTexlFunction5 asyncFunc5)
{
- result = await asyncFunc5.InvokeAsync(_services, node.IRContext.ResultType, args, _cancellationToken).ConfigureAwait(false);
+ BasicServiceProvider services2 = new BasicServiceProvider(_services);
+
+ if (services2.GetService(typeof(TimeZoneInfo)) == null)
+ {
+ services2.AddService(TimeZoneInfo);
+ }
+
+ if (services2.GetService(typeof(Canceller)) == null)
+ {
+ services2.AddService(new Canceller(CheckCancel));
+ }
+
+ result = await asyncFunc5.InvokeAsync(services2, node.IRContext.ResultType, args, _cancellationToken).ConfigureAwait(false);
}
else if (func is IAsyncConnectorTexlFunction asyncConnectorTexlFunction)
{
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs
index 84801e895b..4dc4bb4b23 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs
@@ -2813,7 +2813,7 @@ public static async ValueTask TraceFunction(EvalVisitor runner, Ev
throw new CustomFunctionErrorException(ex.Message);
}
- return FormulaValue.New(true);
+ return irContext.ResultType._type.Kind == DKind.Boolean ? FormulaValue.New(true) : FormulaValue.NewVoid();
}
}
}
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs
index f5e4bdb5c6..3c83962512 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryUntypedObject.cs
@@ -423,7 +423,7 @@ public static FormulaValue CountRows_UO(IRContext irContext, UntypedObjectValue[
{
var impl = args[0].Impl;
- if (impl.Type is ExternalType externalType && externalType.Kind == ExternalTypeKind.Array)
+ if (impl.Type is ExternalType externalType && (externalType.Kind == ExternalTypeKind.Array || externalType.Kind == ExternalTypeKind.ArrayAndObject))
{
return new NumberValue(irContext, impl.GetArrayLength());
}
@@ -452,7 +452,7 @@ public static FormulaValue Column_UO(IRContext irContext, FormulaValue[] args)
var impl = (args[0] as UntypedObjectValue).Impl;
var propertyName = (args[1] as StringValue).Value;
- if (impl.Type is ExternalType externalType && externalType.Kind == ExternalTypeKind.Object)
+ if (impl.Type is ExternalType externalType && (externalType.Kind == ExternalTypeKind.Object || externalType.Kind == ExternalTypeKind.ArrayAndObject))
{
if (impl.TryGetProperty(propertyName, out var propertyValue))
{
diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs b/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs
index 8dd37baae8..c384cfe3d9 100644
--- a/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs
+++ b/src/libraries/Microsoft.PowerFx.Interpreter/RecalcEngine.cs
@@ -460,15 +460,18 @@ private void AddUserDefinedFunctions(IEnumerable parsedUdfs, ReadOnlySymbol
foreach (var udf in udfs)
{
- Config.SymbolTable.AddFunction(udf);
var binding = udf.BindBody(nameResolver, new Glue2DocumentBinderGlue(), BindingConfig.Default, Config.Features);
List bindErrors = new List();
- if (binding.ErrorContainer.GetErrors(ref errors))
+ if (binding.ErrorContainer.GetErrors(ref bindErrors))
{
sb.AppendLine(string.Join(", ", bindErrors.Select(err => err.ToString())));
}
+ else
+ {
+ Config.SymbolTable.AddFunction(udf);
+ }
}
if (sb.Length > 0)
diff --git a/src/libraries/Microsoft.PowerFx.Json/Functions/AsTypeFunction_UOImpl.cs b/src/libraries/Microsoft.PowerFx.Json/Functions/AsTypeFunction_UOImpl.cs
index c09190ea34..d072648229 100644
--- a/src/libraries/Microsoft.PowerFx.Json/Functions/AsTypeFunction_UOImpl.cs
+++ b/src/libraries/Microsoft.PowerFx.Json/Functions/AsTypeFunction_UOImpl.cs
@@ -6,9 +6,7 @@
using System.Threading.Tasks;
using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.IR;
-using Microsoft.PowerFx.Core.Types;
using Microsoft.PowerFx.Core.Utils;
-using Microsoft.PowerFx.Functions;
using Microsoft.PowerFx.Types;
namespace Microsoft.PowerFx.Core.Texl.Builtins
@@ -18,6 +16,7 @@ internal class AsTypeFunction_UOImpl : AsTypeFunction_UO, IAsyncTexlFunction4
public async Task InvokeAsync(TimeZoneInfo timezoneInfo, FormulaType ft, FormulaValue[] args, CancellationToken cancellationToken)
{
Contracts.Assert(args.Length == 2);
+ cancellationToken.ThrowIfCancellationRequested();
var irContext = IRContext.NotInSource(ft);
var typeString = (StringValue)args[1];
diff --git a/src/libraries/Microsoft.PowerFx.Json/Functions/IsTypeFunction_UOImpl.cs b/src/libraries/Microsoft.PowerFx.Json/Functions/IsTypeFunction_UOImpl.cs
index 588f97d3ff..16b1b7fa08 100644
--- a/src/libraries/Microsoft.PowerFx.Json/Functions/IsTypeFunction_UOImpl.cs
+++ b/src/libraries/Microsoft.PowerFx.Json/Functions/IsTypeFunction_UOImpl.cs
@@ -6,9 +6,7 @@
using System.Threading.Tasks;
using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.IR;
-using Microsoft.PowerFx.Core.Types;
using Microsoft.PowerFx.Core.Utils;
-using Microsoft.PowerFx.Functions;
using Microsoft.PowerFx.Types;
namespace Microsoft.PowerFx.Core.Texl.Builtins
@@ -18,6 +16,7 @@ internal class IsTypeFunction_UOImpl : IsTypeFunction_UO, IAsyncTexlFunction4
public async Task InvokeAsync(TimeZoneInfo timezoneInfo, FormulaType ft, FormulaValue[] args, CancellationToken cancellationToken)
{
Contracts.Assert(args.Length == 2);
+ cancellationToken.ThrowIfCancellationRequested();
var irContext = IRContext.NotInSource(FormulaType.UntypedObject);
var typeString = (StringValue)args[1];
diff --git a/src/libraries/Microsoft.PowerFx.Json/Functions/JsonFunctionImpl.cs b/src/libraries/Microsoft.PowerFx.Json/Functions/JsonFunctionImpl.cs
index bc584c180c..0163316dce 100644
--- a/src/libraries/Microsoft.PowerFx.Json/Functions/JsonFunctionImpl.cs
+++ b/src/libraries/Microsoft.PowerFx.Json/Functions/JsonFunctionImpl.cs
@@ -16,24 +16,30 @@
using Microsoft.PowerFx.Core.Entities;
using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.IR;
+using Microsoft.PowerFx.Core.Localization;
using Microsoft.PowerFx.Core.Types.Enums;
using Microsoft.PowerFx.Types;
namespace Microsoft.PowerFx.Core.Texl.Builtins
{
- internal class JsonFunctionImpl : JsonFunction, IAsyncTexlFunction4
+ internal class JsonFunctionImpl : JsonFunction, IAsyncTexlFunction5
{
- public Task InvokeAsync(TimeZoneInfo timezoneInfo, FormulaType type, FormulaValue[] args, CancellationToken cancellationToken)
+ public Task InvokeAsync(IServiceProvider runtimeServiceProvider, FormulaType type, FormulaValue[] args, CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
- return Task.FromResult(new JsonProcessing(timezoneInfo, type, args, supportsLazyTypes).Process());
+ TimeZoneInfo timeZoneInfo = runtimeServiceProvider.GetService(typeof(TimeZoneInfo)) as TimeZoneInfo ?? throw new InvalidOperationException("TimeZoneInfo is required");
+ Canceller canceller = runtimeServiceProvider.GetService(typeof(Canceller)) as Canceller ?? new Canceller(() => cancellationToken.ThrowIfCancellationRequested());
+
+ return Task.FromResult(new JsonProcessing(timeZoneInfo, type, args, supportsLazyTypes).Process(canceller));
}
internal class JsonProcessing
{
private readonly FormulaValue[] _arguments;
+
private readonly FormulaType _type;
+
private readonly TimeZoneInfo _timeZoneInfo;
+
private readonly bool _supportsLazyTypes;
internal JsonProcessing(TimeZoneInfo timezoneInfo, FormulaType type, FormulaValue[] args, bool supportsLazyTypes)
@@ -44,8 +50,10 @@ internal JsonProcessing(TimeZoneInfo timezoneInfo, FormulaType type, FormulaValu
_supportsLazyTypes = supportsLazyTypes;
}
- internal FormulaValue Process()
+ internal FormulaValue Process(Canceller canceller)
{
+ canceller.ThrowIfCancellationRequested();
+
JsonFlags flags = GetFlags();
if (flags == null || JsonFunction.HasUnsupportedType(_arguments[0].Type._type, _supportsLazyTypes, out _, out _))
@@ -61,10 +69,21 @@ internal FormulaValue Process()
using MemoryStream memoryStream = new MemoryStream();
using Utf8JsonWriter writer = new Utf8JsonWriter(memoryStream, new JsonWriterOptions() { Indented = flags.IndentFour, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
- Utf8JsonWriterVisitor jsonWriterVisitor = new Utf8JsonWriterVisitor(writer, _timeZoneInfo, flattenValueTables: flags.FlattenValueTables);
+ Utf8JsonWriterVisitor jsonWriterVisitor = new Utf8JsonWriterVisitor(writer, _timeZoneInfo, flattenValueTables: flags.FlattenValueTables, canceller);
- _arguments[0].Visit(jsonWriterVisitor);
- writer.Flush();
+ try
+ {
+ _arguments[0].Visit(jsonWriterVisitor);
+ writer.Flush();
+ }
+ catch (InvalidOperationException)
+ {
+ if (!jsonWriterVisitor.ErrorValues.Any())
+ {
+ // Unexpected error, rethrow
+ throw;
+ }
+ }
if (jsonWriterVisitor.ErrorValues.Any())
{
@@ -104,7 +123,7 @@ private JsonFlags GetFlags()
optionString = sv.Value;
break;
- // if not one of these, will check optionString != null below
+ // if not one of these, will check optionString != null below
}
if (optionString != null)
@@ -129,17 +148,58 @@ private JsonFlags GetFlags()
private class Utf8JsonWriterVisitor : IValueVisitor
{
+ private const int _maxDepth = 20; // maximum depth of UO
+
+ private const int _maxLength = 1024 * 1024; // 1 MB, maximum number of bytes allowed to be sent to Utf8JsonWriter
+
private readonly Utf8JsonWriter _writer;
+
private readonly TimeZoneInfo _timeZoneInfo;
+
private readonly bool _flattenValueTables;
+ private readonly Canceller _canceller;
+
internal readonly List ErrorValues = new List();
- internal Utf8JsonWriterVisitor(Utf8JsonWriter writer, TimeZoneInfo timeZoneInfo, bool flattenValueTables)
+ internal Utf8JsonWriterVisitor(Utf8JsonWriter writer, TimeZoneInfo timeZoneInfo, bool flattenValueTables, Canceller canceller)
{
_writer = writer;
_timeZoneInfo = timeZoneInfo;
_flattenValueTables = flattenValueTables;
+
+ _canceller = canceller;
+ }
+
+ private void CheckLimitsAndCancellation(int index)
+ {
+ _canceller.ThrowIfCancellationRequested();
+
+ if (index > _maxDepth)
+ {
+ IRContext irContext = IRContext.NotInSource(FormulaType.UntypedObject);
+ ErrorValues.Add(new ErrorValue(irContext, new ExpressionError()
+ {
+ ResourceKey = TexlStrings.ErrReachedMaxJsonDepth,
+ Span = irContext.SourceContext,
+ Kind = ErrorKind.InvalidArgument
+ }));
+
+ throw new InvalidOperationException($"Maximum depth {_maxDepth} reached while traversing JSON payload.");
+ }
+
+ if (_writer.BytesCommitted + _writer.BytesPending > _maxLength)
+ {
+ IRContext irContext = IRContext.NotInSource(FormulaType.UntypedObject);
+ ErrorValues.Add(new ErrorValue(irContext, new ExpressionError()
+ {
+ ResourceKey = TexlStrings.ErrReachedMaxJsonLength,
+ Span = irContext.SourceContext,
+ Kind = ErrorKind.InvalidArgument
+ }));
+
+ throw new InvalidOperationException($"Maximum length {_maxLength} reached in JSON function.");
+ }
}
public void Visit(BlankValue blankValue)
@@ -173,7 +233,7 @@ public void Visit(DecimalValue decimalValue)
}
public void Visit(ErrorValue errorValue)
- {
+ {
ErrorValues.Add(errorValue);
_writer.WriteStringValue("ErrorValue");
}
@@ -256,8 +316,10 @@ public void Visit(RecordValue recordValue)
{
_writer.WriteStartObject();
- foreach (NamedValue namedValue in recordValue.Fields)
+ foreach (NamedValue namedValue in recordValue.Fields.OrderBy(f => f.Name, StringComparer.Ordinal))
{
+ CheckLimitsAndCancellation(0);
+
_writer.WritePropertyName(namedValue.Name);
namedValue.Value.Visit(this);
}
@@ -287,6 +349,8 @@ public void Visit(TableValue tableValue)
foreach (DValue row in tableValue.Rows)
{
+ CheckLimitsAndCancellation(0);
+
if (row.IsBlank)
{
row.Blank.Visit(this);
@@ -319,7 +383,77 @@ public void Visit(TimeValue timeValue)
public void Visit(UntypedObjectValue untypedObjectValue)
{
- throw new ArgumentException($"Unable to serialize type {untypedObjectValue.GetType().FullName} to Json format.");
+ Visit(untypedObjectValue.Impl);
+ }
+
+ private void Visit(IUntypedObject untypedObject, int depth = 0)
+ {
+ FormulaType type = untypedObject.Type;
+
+ CheckLimitsAndCancellation(depth);
+
+ if (type is StringType)
+ {
+ _writer.WriteStringValue(untypedObject.GetString());
+ }
+ else if (type is DecimalType)
+ {
+ _writer.WriteNumberValue(untypedObject.GetDecimal());
+ }
+ else if (type is NumberType)
+ {
+ _writer.WriteNumberValue(untypedObject.GetDouble());
+ }
+ else if (type is BooleanType)
+ {
+ _writer.WriteBooleanValue(untypedObject.GetBoolean());
+ }
+ else if (type is ExternalType externalType)
+ {
+ if (externalType.Kind == ExternalTypeKind.Array || externalType.Kind == ExternalTypeKind.ArrayAndObject)
+ {
+ _writer.WriteStartArray();
+
+ for (var i = 0; i < untypedObject.GetArrayLength(); i++)
+ {
+ CheckLimitsAndCancellation(depth);
+
+ IUntypedObject row = untypedObject[i];
+ Visit(row, depth + 1);
+ }
+
+ _writer.WriteEndArray();
+ }
+ else if ((externalType.Kind == ExternalTypeKind.Object || externalType.Kind == ExternalTypeKind.ArrayAndObject) && untypedObject.TryGetPropertyNames(out IEnumerable propertyNames))
+ {
+ _writer.WriteStartObject();
+
+ foreach (var propertyName in propertyNames.OrderBy(prop => prop, StringComparer.Ordinal))
+ {
+ CheckLimitsAndCancellation(depth);
+
+ if (untypedObject.TryGetProperty(propertyName, out IUntypedObject res))
+ {
+ _writer.WritePropertyName(propertyName);
+ Visit(res, depth + 1);
+ }
+ }
+
+ _writer.WriteEndObject();
+ }
+ else if (externalType.Kind == ExternalTypeKind.UntypedNumber)
+ {
+ _writer.WriteRawValue(untypedObject.GetUntypedNumber());
+ }
+ else
+ {
+ throw new NotSupportedException("Unknown ExternalType");
+ }
+ }
+ else
+ {
+ throw new NotSupportedException("Unknown IUntypedObject");
+ }
}
public void Visit(BlobValue value)
@@ -332,7 +466,7 @@ public void Visit(BlobValue value)
{
_writer.WriteBase64StringValue(value.GetAsByteArrayAsync(CancellationToken.None).Result);
}
- }
+ }
}
internal static string GetColorString(Color color) => $"#{color.R:x2}{color.G:x2}{color.B:x2}{color.A:x2}";
diff --git a/src/libraries/Microsoft.PowerFx.Json/Functions/TypedParseJSONImpl.cs b/src/libraries/Microsoft.PowerFx.Json/Functions/TypedParseJSONImpl.cs
index 9fc5e6d122..fcc3e5667a 100644
--- a/src/libraries/Microsoft.PowerFx.Json/Functions/TypedParseJSONImpl.cs
+++ b/src/libraries/Microsoft.PowerFx.Json/Functions/TypedParseJSONImpl.cs
@@ -6,9 +6,7 @@
using System.Threading.Tasks;
using Microsoft.PowerFx.Core.Functions;
using Microsoft.PowerFx.Core.IR;
-using Microsoft.PowerFx.Core.Types;
using Microsoft.PowerFx.Core.Utils;
-using Microsoft.PowerFx.Functions;
using Microsoft.PowerFx.Types;
namespace Microsoft.PowerFx.Core.Texl.Builtins
@@ -18,6 +16,7 @@ internal class TypedParseJSONFunctionImpl : TypedParseJSONFunction, IAsyncTexlFu
public async Task InvokeAsync(TimeZoneInfo timezoneInfo, FormulaType ft, FormulaValue[] args, CancellationToken cancellationToken)
{
Contracts.Assert(args.Length == 2);
+ cancellationToken.ThrowIfCancellationRequested();
var irContext = IRContext.NotInSource(ft);
var typeString = (StringValue)args[1];
diff --git a/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Handlers/Completions/CompletionsLanguageServerOperationHandler.cs b/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Handlers/Completions/CompletionsLanguageServerOperationHandler.cs
index c46feeda13..1a25bcc941 100644
--- a/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Handlers/Completions/CompletionsLanguageServerOperationHandler.cs
+++ b/src/libraries/Microsoft.PowerFx.LanguageServerProtocol/Handlers/Completions/CompletionsLanguageServerOperationHandler.cs
@@ -145,6 +145,8 @@ private static CompletionItemKind GetCompletionItemKind(SuggestionKind kind)
return CompletionItemKind.Module;
case SuggestionKind.ScopeVariable:
return CompletionItemKind.Variable;
+ case SuggestionKind.Type:
+ return CompletionItemKind.TypeParameter;
default:
return CompletionItemKind.Text;
}
diff --git a/src/libraries/Microsoft.PowerFx.Repl/Repl.cs b/src/libraries/Microsoft.PowerFx.Repl/Repl.cs
index 6b1bf6c1b0..1d0657ff1b 100644
--- a/src/libraries/Microsoft.PowerFx.Repl/Repl.cs
+++ b/src/libraries/Microsoft.PowerFx.Repl/Repl.cs
@@ -34,6 +34,9 @@ public class PowerFxREPL
// Allow repl to create new definitions, such as Set().
public bool AllowSetDefinitions { get; set; }
+ // Allow repl to create new UserDefinedFunctions.
+ public bool AllowUserDefinedFunctions { get; set; }
+
// Do we print each command before evaluation?
// Useful if we're running a file and are debugging, or if input UI is separated from output UI.
public bool Echo { get; set; } = false;
@@ -405,6 +408,30 @@ await this.Output.WriteLineAsync($"Error: Can't set '{name}' to a Void value.",
var errors = check.ApplyErrors();
if (!check.IsSuccess)
{
+ var definitionsCheckResult = new DefinitionsCheckResult();
+
+ definitionsCheckResult.SetText(expression, this.ParserOptions)
+ .ApplyParseErrors();
+
+ if (this.AllowUserDefinedFunctions && definitionsCheckResult.IsSuccess && definitionsCheckResult.ContainsUDF)
+ {
+ var defCheckResult = this.Engine.AddUserDefinedFunction(expression, this.ParserOptions.Culture, extraSymbolTable);
+
+ if (!defCheckResult.IsSuccess)
+ {
+ foreach (var error in defCheckResult.Errors)
+ {
+ var kind = error.IsWarning ? OutputKind.Warning : OutputKind.Error;
+ var msg = error.ToString();
+
+ await this.Output.WriteLineAsync(lineError + msg, kind, cancel)
+ .ConfigureAwait(false);
+ }
+ }
+
+ return new ReplResult();
+ }
+
foreach (var error in check.Errors)
{
var kind = error.IsWarning ? OutputKind.Warning : OutputKind.Error;
diff --git a/src/strings/PowerFxResources.en-US.resx b/src/strings/PowerFxResources.en-US.resx
index b5eda754df..ae17abc96a 100644
--- a/src/strings/PowerFxResources.en-US.resx
+++ b/src/strings/PowerFxResources.en-US.resx
@@ -4119,6 +4119,14 @@
Named formula must be an expression.
This error message shows up when Named formula is not an expression. For example, a = ;
+
+ Named type must be a type literal.
+
+ This error message shows up when Named type is not a type literal. A valid type literal expression is of syntax "Type(Expression)".
+ Some examples for valid named type declarations - "Point := Type({x: Number, y: Number});" , "T1 := Type(Number);" , "T2 := Type([Boolean]);".
+ Some examples for invalid named type declarations - "T1 := 5;" , "T2 := ;" , "T3 := [1, 2, 3];".
+
+
User-defined function must have a body.
This error message shows up when user-defined function does not have a body
@@ -4336,6 +4344,10 @@
Can't delegate {0}: contains a behavior function '{1}'.
Warning message.
+
+ CountRows may return a cached value. Use CountIf(DataSource, true) to get the latest count.
+ {Locked=CountRows}. Warning message when an expression with the CountRows function is used with a data source that caches its size.
+
Determines if the supplied text has a match of the supplied text format.
Description of 'IsMatch' function.
@@ -4760,4 +4772,12 @@
Unsupported untyped/JSON conversion type '{0}' in argument.
Error Message shown to user when a unsupported type is passed in type argument of AsType, IsType and ParseJSON functions.
+
+ Maximum depth reached while traversing JSON payload.
+ Error message returned by the JSON function when a document that is too deeply nested is passed to it. The term JSON in the error refers to the data format described in www.json.org.
+
+
+ Maximum length reached in JSON function.
+ Error message returned by the {Locked=JSON} function when the result generated by this function would be too long.
+
\ No newline at end of file
diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems
index 58658cea68..dc0b202d51 100644
--- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems
+++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Microsoft.PowerFx.Connectors.Tests.Shared.projitems
@@ -225,6 +225,7 @@
+
@@ -269,6 +270,8 @@
+
+
diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs
index ed57736f80..599f5898d1 100644
--- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs
+++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs
@@ -274,9 +274,9 @@ public async Task SAP_CDP()
ConsoleLogger logger = new ConsoleLogger(_output);
using var httpClient = new HttpClient(testConnector);
- string connectionId = "66108f1684944d4994ea38b13d1ee70f";
+ string connectionId = "1e702ce4f10c482684cee1465e686764";
string jwt = "eyJ0eXAi...";
- using var client = new PowerPlatformConnectorClient("f5de196a-41e6-ee09-92cf-664b4f31a6b2.06.common.tip1002.azure-apihub.net", "f5de196a-41e6-ee09-92cf-664b4f31a6b2", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" };
+ using var client = new PowerPlatformConnectorClient("066d5714-1ffc-e316-90bd-affc61d8e6fd.18.common.tip2.azure-apihub.net", "066d5714-1ffc-e316-90bd-affc61d8e6fd", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" };
testConnector.SetResponseFromFile(@"Responses\SAP GetDataSetMetadata.json");
DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, $"/apim/sapodata/{connectionId}", CancellationToken.None, logger);
@@ -292,6 +292,20 @@ public async Task SAP_CDP()
CdpTableValue sapTableValue = sapTable.GetTableValue();
Assert.Equal