diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9f59164c41 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/README.md b/README.md index d31878a02f..21ac795e49 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ The easiest and most powerful ​system to express business logic for ​everyon | Name | Released Package | |------------------------------------------|------------------| -| Microsoft.PowerFx.Core | [![BotBuilder Badge](https://buildstats.info/nuget/Microsoft.PowerFx.Core?includePreReleases=true&dWidth=70)](https://www.nuget.org/packages/Microsoft.PowerFx.Core/) | -| Microsoft.PowerFx.Connectors | [![BotBuilder Badge](https://buildstats.info/nuget/Microsoft.PowerFx.Connectors?includePreReleases=true&dWidth=70)](https://www.nuget.org/packages/Microsoft.PowerFx.Core.Connectors/) | -| Microsoft.PowerFx.Json | [![BotBuilder Badge](https://buildstats.info/nuget/Microsoft.PowerFx.Json?includePreReleases=true&dWidth=70)](https://www.nuget.org/packages/Microsoft.PowerFx.Core.Json/) | -| Microsoft.PowerFx.Core.Tests | [![BotBuilder Badge](https://buildstats.info/nuget/Microsoft.PowerFx.Core.Tests?includePreReleases=true&dWidth=70)](https://www.nuget.org/packages/Microsoft.PowerFx.Core.Tests/) | -| Microsoft.PowerFx.Interpreter | [![BotBuilder Badge](https://buildstats.info/nuget/Microsoft.PowerFx.Interpreter?includePreReleases=true&dWidth=70)](https://www.nuget.org/packages/Microsoft.PowerFx.Interpreter/) | -| Microsoft.PowerFx.LanguageServerProtocol | [![BotBuilder Badge](https://buildstats.info/nuget/Microsoft.PowerFx.LanguageServerProtocol?includePreReleases=true&dWidth=70)](https://www.nuget.org/packages/Microsoft.PowerFx.LanguageServerProtocol/) | -| Microsoft.PowerFx.Transport.Attributes | [![BotBuilder Badge](https://buildstats.info/nuget/Microsoft.PowerFx.Transport.Attributes?includePreReleases=true&dWidth=70)](https://www.nuget.org/packages/Microsoft.PowerFx.Transport.Attributes/) | +| Microsoft.PowerFx.Core | [![BotBuilder Badge](https://img.shields.io/nuget/vpre/Microsoft.PowerFx.Core?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.PowerFx.Core/) | +| Microsoft.PowerFx.Connectors | [![BotBuilder Badge](https://img.shields.io/nuget/vpre/Microsoft.PowerFx.Connectors?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.PowerFx.Core.Connectors/) | +| Microsoft.PowerFx.Json | [![BotBuilder Badge](https://img.shields.io/nuget/vpre/Microsoft.PowerFx.Json?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.PowerFx.Core.Json/) | +| Microsoft.PowerFx.Core.Tests | [![BotBuilder Badge](https://img.shields.io/nuget/vpre/Microsoft.PowerFx.Core.Tests?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.PowerFx.Core.Tests/) | +| Microsoft.PowerFx.Interpreter | [![BotBuilder Badge](https://img.shields.io/nuget/vpre/Microsoft.PowerFx.Interpreter?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.PowerFx.Interpreter/) | +| Microsoft.PowerFx.LanguageServerProtocol | [![BotBuilder Badge](https://img.shields.io/nuget/vpre/Microsoft.PowerFx.LanguageServerProtocol?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.PowerFx.LanguageServerProtocol/) | +| Microsoft.PowerFx.Transport.Attributes | [![BotBuilder Badge](https://img.shields.io/nuget/vpre/Microsoft.PowerFx.Transport.Attributes?label=Latest&logo=nuget)](https://www.nuget.org/packages/Microsoft.PowerFx.Transport.Attributes/) | ## Daily Builds Daily builds of the Power Fx packages are published to Azure Artifacts. diff --git a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorConstants.cs b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorConstants.cs index 49f1d1f0bd..e8f26dda8c 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorConstants.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorConstants.cs @@ -10,6 +10,7 @@ public static class Constants public const string XMsDynamicProperties = "x-ms-dynamic-properties"; public const string XMsDynamicSchema = "x-ms-dynamic-schema"; public const string XMsDynamicValues = "x-ms-dynamic-values"; + public const string XMsEnum = "x-ms-enum"; public const string XMsEnumDisplayName = "x-ms-enum-display-name"; public const string XMsEnumValues = "x-ms-enum-values"; public const string XMsExplicitInput = "x-ms-explicit-input"; diff --git a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs index 15b629701e..c01b9b2b6a 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs @@ -17,6 +17,7 @@ using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Validations; using Microsoft.PowerFx.Connectors.Localization; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.Errors; using Microsoft.PowerFx.Core.Localization; using Microsoft.PowerFx.Core.Types; @@ -867,7 +868,7 @@ private async Task PostProcessResultAsync(FormulaValue result, Bas ExpressionError newError = er is HttpExpressionError her ? new HttpExpressionError(her.StatusCode) { Kind = er.Kind, Severity = er.Severity, Message = $"{DPath.Root.Append(new DName(Namespace)).ToDottedSyntax()}.{Name} failed: {er.Message}" } : new ExpressionError() { Kind = er.Kind, Severity = er.Severity, Message = $"{DPath.Root.Append(new DName(Namespace)).ToDottedSyntax()}.{Name} failed: {er.Message}" }; - result = FormulaValue.NewError(newError, ev.Type); + result = FormulaValue.NewError(newError, ev.Type); } if (IsPageable && result is RecordValue rv) @@ -1005,33 +1006,23 @@ internal static ConnectorType GetConnectorType(string valuePath, StringValue sv, } // Only called by ConnectorTable.GetSchema - internal static ConnectorType GetConnectorTypeAndTableCapabilities(ICdpTableResolver tableResolver, string connectorName, string valuePath, StringValue sv, List sqlRelationships, ConnectorCompatibility compatibility, string datasetName, out string name, out string displayName, out ServiceCapabilities tableCapabilities) + // Returns a FormulaType with AssociatedDataSources set (done in AddTabularDataSource) + internal static ConnectorType GetCdpTableType(ICdpTableResolver tableResolver, string connectorName, string valuePath, StringValue stringValue, List sqlRelationships, ConnectorCompatibility compatibility, string datasetName, out string name, out string displayName, out TableDelegationInfo delegationInfo) { // There are some errors when parsing this Json payload but that's not a problem here as we only need x-ms-capabilities parsing to work OpenApiReaderSettings oars = new OpenApiReaderSettings() { RuleSet = DefaultValidationRuleSet }; - ISwaggerSchema tableSchema = SwaggerSchema.New(new OpenApiStringReader(oars).ReadFragment(sv.Value, OpenApi.OpenApiSpecVersion.OpenApi2_0, out OpenApiDiagnostic _)); - tableCapabilities = tableSchema.GetTableCapabilities(); - - JsonElement je = ExtractFromJson(sv, valuePath, out name, out displayName); - - // Json version to be able to read SalesForce unique properties - ConnectorType connectorType = GetJsonConnectorTypeInternal(compatibility, je, sqlRelationships); - connectorType.Name = name; - IList referencedEntities = GetReferenceEntities(connectorName, sv); + ISwaggerSchema tableSchema = SwaggerSchema.New(new OpenApiStringReader(oars).ReadFragment(stringValue.Value, OpenApi.OpenApiSpecVersion.OpenApi2_0, out OpenApiDiagnostic _)); + ServiceCapabilities serviceCapabilities = tableSchema.GetTableCapabilities(); ConnectorPermission tablePermission = tableSchema.GetPermission(); - bool isTableReadOnly = tablePermission == ConnectorPermission.PermissionReadOnly; - - List primaryKeyParts = connectorType.Fields.Where(f => f.KeyType == ConnectorKeyType.Primary).OrderBy(f => f.KeyOrder).ToList(); - - if (primaryKeyParts.Count == 0) - { - // $$$ need to check what triggers RO for SQL - //isTableReadOnly = true; - } - - connectorType.AddTabularDataSource(tableResolver, referencedEntities, sqlRelationships, new DName(name), datasetName, connectorType, tableCapabilities, isTableReadOnly); - + + JsonElement jsonElement = ExtractFromJson(stringValue, valuePath, out name, out displayName); + bool isTableReadOnly = tablePermission == ConnectorPermission.PermissionReadOnly; + IList referencedEntities = GetReferenceEntities(connectorName, stringValue); + + ConnectorType connectorType = new ConnectorType(jsonElement, compatibility, sqlRelationships, referencedEntities, datasetName, name, connectorName, tableResolver, serviceCapabilities, isTableReadOnly); + delegationInfo = ((DataSourceInfo)connectorType.FormulaType._type.AssociatedDataSources.First()).DelegationInfo; + return connectorType; } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Internal/CdpDtype.cs b/src/libraries/Microsoft.PowerFx.Connectors/Internal/CdpDtype.cs deleted file mode 100644 index 9ce0b8c12a..0000000000 --- a/src/libraries/Microsoft.PowerFx.Connectors/Internal/CdpDtype.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using Microsoft.PowerFx.Core.Entities; -using Microsoft.PowerFx.Core.Types; -using Microsoft.PowerFx.Types; - -namespace Microsoft.PowerFx.Connectors -{ - // Used in TabularIRVisitor - internal class CdpDtype : DType - { - internal TableType TableType; - - internal CdpDtype(TableType tableType) - : base(DKind.Table, tableType._type.TypeTree, null, tableType._type.DisplayNameProvider) - { - TableType = tableType; - - if (tableType._type.AssociatedDataSources != null) - { - foreach (IExternalTabularDataSource ds in tableType._type.AssociatedDataSources) - { - AssociatedDataSources.Add(ds); - } - } - } - } -} diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Microsoft.PowerFx.Connectors.csproj b/src/libraries/Microsoft.PowerFx.Connectors/Microsoft.PowerFx.Connectors.csproj index cf2eb8ebd4..afd66c67ab 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Microsoft.PowerFx.Connectors.csproj +++ b/src/libraries/Microsoft.PowerFx.Connectors/Microsoft.PowerFx.Connectors.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs index 5a03c8dd3b..f884313116 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/OpenApiExtensions.cs @@ -333,6 +333,8 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form internal static bool IsInternal(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.IsInternal() ?? false; internal static string GetVisibility(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.GetVisibility(); + + internal static string GetEnumName(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.GetEnumName(); // Internal parameters are not showen to the user. // They can have a default value or be special cased by the infrastructure (like "connectionId"). @@ -340,6 +342,13 @@ internal static bool TryGetOpenApiValue(IOpenApiAny openApiAny, FormulaType form internal static string GetVisibility(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsVisibility, out IOpenApiExtension openApiExt) && openApiExt is OpenApiString openApiStr ? openApiStr.Value : null; + internal static string GetEnumName(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsEnum, out IOpenApiExtension openApiExt) && + openApiExt is SwaggerJsonObject jsonObject && + jsonObject.TryGetValue("name", out IOpenApiAny enumName) && + enumName is OpenApiString enumNameStr + ? enumNameStr.Value + : null; + internal static string GetMediaKind(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsMediaKind, out IOpenApiExtension openApiExt) && openApiExt is OpenApiString openApiStr ? openApiStr.Value : null; internal static (bool IsPresent, string Value) GetString(this IDictionary apiObj, string str) => apiObj.TryGetValue(str, out IOpenApiAny openApiAny) && openApiAny is OpenApiString openApiStr ? (true, openApiStr.Value) : (false, null); @@ -438,7 +447,8 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar { if (schema.Enum.All(e => e is OpenApiString)) { - OptionSet optionSet = new OptionSet("enum", schema.Enum.Select(e => new DName((e as OpenApiString).Value)).ToDictionary(k => k, e => e).ToImmutableDictionary()); + string enumName = schema.GetEnumName() ?? "enum"; + OptionSet optionSet = new OptionSet(enumName, schema.Enum.Select(e => new DName((e as OpenApiString).Value)).ToDictionary(k => k, e => e).ToImmutableDictionary()); return new ConnectorType(schema, openApiParameter, optionSet.FormulaType); } else @@ -466,14 +476,19 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar case null: case "decimal": case "currency": - return new ConnectorType(schema, openApiParameter, FormulaType.Decimal); + return new ConnectorType(schema, openApiParameter, FormulaType.Decimal); default: return new ConnectorType(error: $"Unsupported type of number: {schema.Format}"); } + // For testing only + case "fxnumber": + return new ConnectorType(schema, openApiParameter, FormulaType.Number); + // Always a boolean (Format not used) - case "boolean": return new ConnectorType(schema, openApiParameter, FormulaType.Boolean); + case "boolean": + return new ConnectorType(schema, openApiParameter, FormulaType.Boolean); // OpenAPI spec: Format could be , int32, int64 case "integer": diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpDelegationInfo.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpDelegationInfo.cs new file mode 100644 index 0000000000..8628ba9d25 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpDelegationInfo.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.PowerFx.Core.Entities +{ + // Used by ServiceCapabilities.ToDelegationInfo for managing CDP x-ms-capabilities + internal class CdpDelegationInfo : TableDelegationInfo + { + public override ColumnCapabilitiesDefinition GetColumnCapability(string fieldName) + { + if (ColumnsCapabilities.TryGetValue(fieldName, out ColumnCapabilitiesBase columnCapabilitiesBase)) + { + return columnCapabilitiesBase switch + { + ColumnCapabilities columnCapabilities => columnCapabilities.Definition, + _ => throw new NotImplementedException($"{columnCapabilitiesBase.GetType().Name} not supported yet") + }; + } + + return null; + } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpRecordType.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpRecordType.cs index 373972176c..47c6e565aa 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpRecordType.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpRecordType.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; -using Microsoft.PowerFx.Core.Types; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Types; namespace Microsoft.PowerFx.Connectors @@ -14,19 +14,13 @@ internal class CdpRecordType : RecordType { internal ConnectorType ConnectorType { get; } - internal IList ReferencedEntities { get; } - - internal IList SqlRelationships { get; } - internal ICdpTableResolver TableResolver { get; } - internal CdpRecordType(ConnectorType connectorType, DType recordType, ICdpTableResolver tableResolver, IList referencedEntities, IList sqlRelationships) - : base(recordType) + internal CdpRecordType(ConnectorType connectorType, ICdpTableResolver tableResolver, TableDelegationInfo delegationInfo) + : base(connectorType.DisplayNameProvider, delegationInfo) { ConnectorType = connectorType; TableResolver = tableResolver; - ReferencedEntities = referencedEntities; - SqlRelationships = sqlRelationships; } public bool TryGetFieldExternalTableName(string fieldName, out string tableName, out string foreignKey) @@ -34,14 +28,9 @@ public bool TryGetFieldExternalTableName(string fieldName, out string tableName, tableName = null; foreignKey = null; - if (!base.TryGetBackingDType(fieldName, out _)) - { - return false; - } - ConnectorType connectorType = ConnectorType.Fields.First(ct => ct.Name == fieldName); - if (connectorType.ExternalTables?.Any() != true) + if (connectorType == null || connectorType.ExternalTables?.Any() != true) { return false; } @@ -51,28 +40,33 @@ public bool TryGetFieldExternalTableName(string fieldName, out string tableName, return true; } - public override bool TryGetFieldType(string fieldName, out FormulaType type) + public override bool TryGetUnderlyingFieldType(string name, out FormulaType type) => TryGetFieldType(name, true, out type); + + public override bool TryGetFieldType(string name, out FormulaType type) => TryGetFieldType(name, false, out type); + + private bool TryGetFieldType(string fieldName, bool ignorelationship, out FormulaType type) { - if (!base.TryGetBackingDType(fieldName, out _)) + ConnectorType field = ConnectorType.Fields.FirstOrDefault(ct => ct.Name == fieldName); + + if (field == null) { type = null; return false; } - ConnectorType ct = ConnectorType.Fields.First(ct => ct.Name == fieldName); - - if (ct.ExternalTables?.Any() != true) + if (field.ExternalTables?.Any() != true || ignorelationship) { - return base.TryGetFieldType(fieldName, out type); + type = field.FormulaType; + return true; } - string tableName = ct.ExternalTables.First(); + string tableName = field.ExternalTables.First(); try { - CdpTableDescriptor ttd = TableResolver.ResolveTableAsync(tableName, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); + ConnectorType connectorType = TableResolver.ResolveTableAsync(tableName, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); - type = ttd.ConnectorType.FormulaType; + type = connectorType.FormulaType; return true; } catch (Exception ex) @@ -84,7 +78,17 @@ public override bool TryGetFieldType(string fieldName, out FormulaType type) public override bool Equals(object other) { - throw new NotImplementedException(); + if (object.ReferenceEquals(this, other)) + { + return true; + } + + if (other is not CdpRecordType otherRecordType) + { + return false; + } + + return ConnectorType.Equals(otherRecordType.ConnectorType); } public override int GetHashCode() @@ -94,6 +98,6 @@ public override int GetHashCode() public override string TableSymbolName => ConnectorType.Name; - public override IEnumerable FieldNames => _type.GetRootFieldNames().Select(name => name.Value); + public override IEnumerable FieldNames => ConnectorType.Fields.Select(field => field.Name); } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpTableType.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpTableType.cs deleted file mode 100644 index 1cdbec0856..0000000000 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpTableType.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using Microsoft.PowerFx.Core.Types; -using Microsoft.PowerFx.Types; - -namespace Microsoft.PowerFx.Connectors -{ - // Used in ConnectorTableValue - internal class CdpTableType : TableType - { - public CdpTableType(TableType tableType) - : base(new CdpDtype(tableType)) - { - } - - public override void Visit(ITypeVisitor vistor) - { - throw new System.NotImplementedException(); - } - } -} diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpTableValue.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpTableValue.cs index fd2aa627d5..24f86efa87 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpTableValue.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/CdpTableValue.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Microsoft.PowerFx.Core.Entities; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.IR; using Microsoft.PowerFx.Types; @@ -18,22 +18,21 @@ public class CdpTableValue : TableValue, IRefreshable, IDelegatableTableValue { public bool IsDelegable => _tabularService.IsDelegable; - protected internal readonly CdpService _tabularService; + protected internal readonly CdpService _tabularService; - protected internal readonly ConnectorType _connectorType; + internal readonly IReadOnlyDictionary Relationships; private IReadOnlyCollection> _cachedRows; internal readonly HttpClient HttpClient; - public RecordType TabularRecordType => _tabularService?.TabularRecordType; + public RecordType RecordType => _tabularService?.RecordType; - public CdpTableValue(CdpService tabularService, ConnectorType connectorType) - : base(IRContext.NotInSource(new CdpTableType(tabularService.TableType))) + internal CdpTableValue(CdpService tabularService, IReadOnlyDictionary relationships) + : base(IRContext.NotInSource(tabularService.TableType)) { _tabularService = tabularService; - _connectorType = connectorType; - + Relationships = relationships; HttpClient = tabularService.HttpClient; } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs index b5331171b6..eee366f31a 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs @@ -8,10 +8,8 @@ using System.Text.Json; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; -using Microsoft.PowerFx.Core.Entities; +using Microsoft.PowerFx.Core; using Microsoft.PowerFx.Core.Localization; -using Microsoft.PowerFx.Core.Types; -using Microsoft.PowerFx.Core.UtilityDataStructures; using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Types; using static Microsoft.PowerFx.Connectors.Constants; @@ -205,6 +203,19 @@ internal ConnectorType(JsonElement schema, ConnectorCompatibility compatibility, { } + internal ConnectorType(JsonElement schema, ConnectorCompatibility compatibility, IList sqlRelationships, IList referencedEntities, string datasetName, string name, string connectorName, ICdpTableResolver resolver, ServiceCapabilities serviceCapabilities, bool isTableReadOnly) + : this(SwaggerJsonSchema.New(schema), null, new SwaggerParameter(null, true, SwaggerJsonSchema.New(schema), null).GetConnectorType(compatibility, sqlRelationships)) + { + Name = name; + + foreach (ConnectorType field in Fields.Where(f => f.Capabilities != null)) + { + serviceCapabilities.AddColumnCapability(field.Name, field.Capabilities); + } + + FormulaType = new CdpRecordType(this, resolver, ServiceCapabilities.ToDelegationInfo(serviceCapabilities, name, isTableReadOnly, this, datasetName)); + } + internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter, ConnectorType connectorType) : this(schema, openApiParameter, connectorType.FormulaType) { @@ -263,33 +274,24 @@ internal ConnectorType(ConnectorType connectorType, ConnectorType[] fields, Form _warnings = connectorType._warnings; } + internal DisplayNameProvider DisplayNameProvider + { + get + { + _displayNameProvider ??= new SingleSourceDisplayNameProvider(Fields.Select(field => new KeyValuePair(new DName(field.Name), new DName(field.DisplayName ?? field.Name)))); + return _displayNameProvider; + } + } + + private DisplayNameProvider _displayNameProvider; + internal void SetRelationship(SqlRelationship relationship) { ExternalTables ??= new List(); ExternalTables.Add(relationship.ReferencedTable); RelationshipName = relationship.RelationshipName; ForeignKey = relationship.ReferencedColumnName; - } - - internal void AddTabularDataSource(ICdpTableResolver tableResolver, IList referencedEntities, List sqlRelationships, DName name, string datasetName, ConnectorType connectorType, ServiceCapabilities serviceCapabilities, bool isReadOnly, BidirectionalDictionary displayNameMapping = null) - { - if (FormulaType is not RecordType) - { - throw new PowerFxConnectorException("Invalid FormulaType"); - } - - // $$$ Hack to enable IExternalTabularDataSource, will be removed later -#pragma warning disable CS0618 // Type or member is obsolete - if (tableResolver.GenerateADS) - { - HashSet dataSource = new HashSet() { new ExternalCdpDataSource(name, datasetName, serviceCapabilities, isReadOnly, displayNameMapping) }; - DType newDType = DType.CreateDTypeWithConnectedDataSourceInfoMetadata(FormulaType._type, dataSource, null); - FormulaType = new KnownRecordType(newDType); - } -#pragma warning restore CS0618 // Type or member is obsolete - - FormulaType = new CdpRecordType(connectorType, FormulaType._type, tableResolver, referencedEntities, sqlRelationships); - } + } private void AggregateErrors(ConnectorType[] types) { diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/CapabilityConstants.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/CapabilityConstants.cs index d11b3b965e..bd6eacdb3d 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/CapabilityConstants.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/CapabilityConstants.cs @@ -18,6 +18,7 @@ internal static class CapabilityConstants public const string FilterRequiredProperties = "requiredProperties"; public const string FilterRestrictions = "filterRestrictions"; public const string GroupRestriction = "groupRestriction"; + public const string IsChoiceValue = "Value"; public const string IsDelegable = "isDelegable"; public const string IsOnlyServerPagable = "isOnlyServerPagable"; public const string IsPageable = "isPageable"; @@ -36,6 +37,6 @@ internal static class CapabilityConstants public const string SPQueryName = "OdataQueryName"; public const string SupportsRecordPermission = "supportsRecordPermission"; public const string UngroupableProperties = "ungroupableProperties"; - public const string UnsortableProperties = "unsortableProperties"; + public const string UnsortableProperties = "unsortableProperties"; } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ColumnCapabilities.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ColumnCapabilities.cs index 27a583a3c7..79656e7f00 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ColumnCapabilities.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ColumnCapabilities.cs @@ -19,12 +19,22 @@ internal sealed class ColumnCapabilities : ColumnCapabilitiesBase, IColumnsCapab [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary Properties => _childColumnsCapabilities.Any() ? _childColumnsCapabilities : null; - private readonly Dictionary _childColumnsCapabilities; + private Dictionary _childColumnsCapabilities; [JsonInclude] [JsonPropertyName(Constants.XMsCapabilities)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly ColumnCapabilitiesDefinition Capabilities; + public ColumnCapabilitiesDefinition Capabilities; + + public static ColumnCapabilities DefaultColumnCapabilities => new ColumnCapabilities() + { + Capabilities = new ColumnCapabilitiesDefinition(null), + _childColumnsCapabilities = new Dictionary() + }; + + private ColumnCapabilities() + { + } public void AddColumnCapability(string name, ColumnCapabilitiesBase capability) { @@ -56,7 +66,11 @@ public static ColumnCapabilities ParseColumnCapabilities(IDictionary FilterFunctions { get; } + // used in PowerApps-Client/src/AppMagic/js/Core/Core.Data/ConnectedDataDeserialization/TabularDataDeserialization.ts [JsonInclude] [JsonPropertyName(CapabilityConstants.PropertyQueryAlias)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly string QueryAlias; + internal string QueryAlias { get; init; } [JsonInclude] [JsonPropertyName(CapabilityConstants.SPIsChoice)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public readonly bool? IsChoice; - - public ColumnCapabilitiesDefinition(string[] filterFunction, string alias, bool? isChoice) - { - Contracts.AssertValueOrNull(filterFunction); - Contracts.AssertValueOrNull(alias); - - FilterFunctions = filterFunction; - QueryAlias = alias; - IsChoice = isChoice; + internal bool? IsChoice { get; init; } + + public ColumnCapabilitiesDefinition(IEnumerable filterFunction) + { + // ex: lt, le, eq, ne, gt, ge, and, or, not, contains, startswith, endswith, countdistinct, day, month, year, time + FilterFunctions = filterFunction; } } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/FilterRestriction.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/FilterRestriction.cs index e0690fa763..c3f4dd0415 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/FilterRestriction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/FilterRestriction.cs @@ -15,19 +15,22 @@ internal sealed class FilterRestriction [JsonInclude] [JsonPropertyName(CapabilityConstants.FilterRequiredProperties)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly List RequiredProperties; + public readonly IList RequiredProperties; [JsonInclude] [JsonPropertyName(CapabilityConstants.NonFilterableProperties)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly List NonFilterableProperties; + public readonly IList NonFilterableProperties; - public FilterRestriction(List requiredProperties, List nonFilterableProperties) + public FilterRestriction(IList requiredProperties, IList nonFilterableProperties) { Contracts.AssertValueOrNull(requiredProperties); Contracts.AssertValueOrNull(nonFilterableProperties); + // List of required properties RequiredProperties = requiredProperties; + + // List of non filterable properties NonFilterableProperties = nonFilterableProperties; } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/GroupRestriction.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/GroupRestriction.cs index 52a1dce880..9063f3d31d 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/GroupRestriction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/GroupRestriction.cs @@ -15,9 +15,9 @@ internal sealed class GroupRestriction [JsonInclude] [JsonPropertyName(CapabilityConstants.UngroupableProperties)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly List UngroupableProperties; + public readonly IList UngroupableProperties; - public GroupRestriction(List ungroupableProperties) + public GroupRestriction(IList ungroupableProperties) { Contracts.AssertValueOrNull(ungroupableProperties); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/PagingCapabilities.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/PagingCapabilities.cs index bfd74d7bde..631dfdd9bd 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/PagingCapabilities.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/PagingCapabilities.cs @@ -1,17 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +using System.Collections.Generic; + namespace Microsoft.PowerFx.Connectors { internal sealed class PagingCapabilities { public readonly bool IsOnlyServerPagable; - public readonly string[] ServerPagingOptions; + public readonly IEnumerable ServerPagingOptions; public PagingCapabilities(bool isOnlyServerPagable, string[] serverPagingOptions) { + // Server paging restrictions, true for CDS + // Setting 'IsOnlyServerPagable' to true in the table metadata response lets PowerApps application to use + // @odata.nextlink URL in reponse message (instead of $skip and $top query parameters) for page traversal. + // It is also required to set sortable and filterable restrictions for PowerApps to page through results. IsOnlyServerPagable = isOnlyServerPagable; + + // List of supported server-driven paging capabilities, null for CDS + // ex: top, skiptoken + // used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/AppMagic/js/AppMagic.Services/ConnectedData/CdpConnector.ts&_a=contents&version=GBmaster ServerPagingOptions = serverPagingOptions; } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SelectionRestriction.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SelectionRestriction.cs index 72d404da81..c523296691 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SelectionRestriction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SelectionRestriction.cs @@ -16,6 +16,8 @@ internal sealed class SelectionRestriction public SelectionRestriction(bool isSelectable) { + // Indicates whether this table has selectable columns + // Used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/Cloud/DocumentServer.Core/Document/Document/InfoTypes/CdsDataSourceInfo.cs&_a=contents&version=GBmaster IsSelectable = isSelectable; } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ServiceCapabilities.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ServiceCapabilities.cs index d03d07a04d..545b88bfd2 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ServiceCapabilities.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/ServiceCapabilities.cs @@ -1,9 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +using System; using System.Collections.Generic; +using System.Linq; using System.Text.Json.Serialization; using Microsoft.OpenApi.Any; +using Microsoft.PowerFx.Core.Entities; +using Microsoft.PowerFx.Core.Functions.Delegation; using Microsoft.PowerFx.Core.Utils; // DO NOT INCLUDE Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata ASSEMBLY @@ -52,17 +56,21 @@ internal sealed class ServiceCapabilities : IColumnsCapabilities [JsonInclude] [JsonPropertyName(CapabilityConstants.FilterFunctions)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly string[] FilterFunctions; + public readonly IEnumerable FilterFunctions; + + public IEnumerable FilterFunctionsEnum => GetDelegationOperatorEnumList(FilterFunctions); [JsonInclude] [JsonPropertyName(CapabilityConstants.FilterFunctionSupport)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly string[] FilterSupportedFunctions; + public readonly IEnumerable FilterSupportedFunctions; + + public IEnumerable FilterSupportedFunctionsEnum => GetDelegationOperatorEnumList(FilterSupportedFunctions); [JsonInclude] [JsonPropertyName(CapabilityConstants.ServerPagingOptions)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string[] ServerPagingOptions => PagingCapabilities.ServerPagingOptions; + public IEnumerable ServerPagingOptions => PagingCapabilities.ServerPagingOptions; [JsonInclude] [JsonPropertyName(CapabilityConstants.IsOnlyServerPagable)] @@ -95,8 +103,8 @@ internal sealed class ServiceCapabilities : IColumnsCapabilities public const int CurrentODataVersion = 4; - public ServiceCapabilities(SortRestriction sortRestriction, FilterRestriction filterRestriction, SelectionRestriction selectionRestriction, GroupRestriction groupRestriction, string[] filterFunctions, string[] filterSupportedFunctions, - PagingCapabilities pagingCapabilities, bool recordPermissionCapabilities, int oDataVersion = CurrentODataVersion, bool supportsDataverseOffline = false) + public ServiceCapabilities(SortRestriction sortRestriction, FilterRestriction filterRestriction, SelectionRestriction selectionRestriction, GroupRestriction groupRestriction, IEnumerable filterFunctions, + IEnumerable filterSupportedFunctions, PagingCapabilities pagingCapabilities, bool recordPermissionCapabilities, int oDataVersion = CurrentODataVersion, bool supportsDataverseOffline = false) { Contracts.AssertValueOrNull(sortRestriction); Contracts.AssertValueOrNull(filterRestriction); @@ -122,16 +130,102 @@ public ServiceCapabilities(SortRestriction sortRestriction, FilterRestriction fi SupportsRecordPermission = recordPermissionCapabilities; } - public void AddColumnCapability(string name, ColumnCapabilitiesBase capability) + public static TableDelegationInfo ToDelegationInfo(ServiceCapabilities serviceCapabilities, string tableName, bool isReadOnly, ConnectorType connectorType, string datasetName) { - Contracts.AssertNonEmpty(name); - Contracts.AssertValue(capability); + // sortRestriction == null means sortable = false + SortRestrictions sortRestriction = serviceCapabilities?.SortRestriction != null + ? new SortRestrictions() + { + AscendingOnlyProperties = serviceCapabilities.SortRestriction.AscendingOnlyProperties, + UnsortableProperties = serviceCapabilities.SortRestriction.UnsortableProperties + } + : null; + + FilterRestrictions filterRestriction = new FilterRestrictions() + { + RequiredProperties = serviceCapabilities?.FilterRestriction?.RequiredProperties, + NonFilterableProperties = serviceCapabilities?.FilterRestriction?.NonFilterableProperties + }; + + // selectionRestriction == null means selectable = false + SelectionRestrictions selectionRestriction = serviceCapabilities?.SelectionRestriction != null + ? new SelectionRestrictions() + { + IsSelectable = serviceCapabilities.SelectionRestriction.IsSelectable + } + : null; + + GroupRestrictions groupRestriction = new GroupRestrictions() + { + UngroupableProperties = serviceCapabilities?.GroupRestriction?.UngroupableProperties + }; + + Core.Entities.PagingCapabilities pagingCapabilities = new Core.Entities.PagingCapabilities() + { + IsOnlyServerPagable = serviceCapabilities?.PagingCapabilities?.IsOnlyServerPagable ?? false, + ServerPagingOptions = serviceCapabilities?.PagingCapabilities?.ServerPagingOptions?.ToArray() + }; + + Dictionary columnCapabilities = serviceCapabilities?._columnsCapabilities?.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value switch + { + ColumnCapabilities cc => new Core.Entities.ColumnCapabilities(new Core.Entities.ColumnCapabilitiesDefinition() + { + FilterFunctions = GetDelegationOperatorEnumList(cc.Capabilities.FilterFunctions), + QueryAlias = cc.Capabilities.QueryAlias, + IsChoice = cc.Capabilities.IsChoice + }) as Core.Entities.ColumnCapabilitiesBase, + ComplexColumnCapabilities ccc => new Core.Entities.ComplexColumnCapabilities() as Core.Entities.ColumnCapabilitiesBase, + _ => throw new NotImplementedException() + }); + + Dictionary columnWithRelationships = connectorType.Fields.Where(f => f.ExternalTables?.Any() == true).Select(f => (f.Name, f.ExternalTables.First())).ToDictionary(tpl => tpl.Name, tpl => tpl.Item2); + + return new CdpDelegationInfo() + { + TableName = tableName, + IsReadOnly = isReadOnly, + DatasetName = datasetName, + SortRestriction = sortRestriction, + FilterRestriction = filterRestriction, + SelectionRestriction = selectionRestriction, + GroupRestriction = groupRestriction, + FilterFunctions = serviceCapabilities?.FilterFunctionsEnum, + FilterSupportedFunctions = serviceCapabilities?.FilterSupportedFunctionsEnum, + PagingCapabilities = pagingCapabilities, + SupportsRecordPermission = serviceCapabilities?.SupportsRecordPermission ?? false, + ColumnsCapabilities = columnCapabilities, + ColumnsWithRelationships = columnWithRelationships + }; + } + + private static IEnumerable GetDelegationOperatorEnumList(IEnumerable filterFunctionList) + { + if (filterFunctionList == null) + { + return null; + } - if (_columnsCapabilities == null) + List list = new List(); + + foreach (string str in filterFunctionList) { - _columnsCapabilities = new Dictionary(); + if (Enum.TryParse(str, true, out DelegationOperator op)) + { + list.Add(op); + } } + return list; + } + + public void AddColumnCapability(string name, ColumnCapabilitiesBase capability) + { + Contracts.AssertNonEmpty(name); + Contracts.AssertValue(capability); + + _columnsCapabilities ??= new Dictionary(); _columnsCapabilities.Add(name, capability); } @@ -160,32 +254,34 @@ private static FilterRestriction ParseFilterRestriction(IDictionary filterRestritionMetaData = capabilitiesMetaData.GetObject(CapabilityConstants.FilterRestrictions); return filterRestritionMetaData?.GetBool(CapabilityConstants.Filterable) == true - ? new FilterRestriction(filterRestritionMetaData.GetList(CapabilityConstants.FilterRequiredProperties), filterRestritionMetaData.GetList(CapabilityConstants.NonFilterableProperties)) - : null; + ? new FilterRestriction(filterRestritionMetaData.GetList(CapabilityConstants.FilterRequiredProperties), filterRestritionMetaData.GetList(CapabilityConstants.NonFilterableProperties)) + : null; } private static SortRestriction ParseSortRestriction(IDictionary capabilitiesMetaData) { IDictionary sortRestrictionMetaData = capabilitiesMetaData.GetObject(CapabilityConstants.SortRestrictions); + + // When "sortable" = false (or not defined), SortRestriction is null return sortRestrictionMetaData?.GetBool(CapabilityConstants.Sortable) == true - ? new SortRestriction(sortRestrictionMetaData.GetList(CapabilityConstants.UnsortableProperties), sortRestrictionMetaData.GetList(CapabilityConstants.AscendingOnlyProperties)) - : null; + ? new SortRestriction(sortRestrictionMetaData.GetList(CapabilityConstants.UnsortableProperties), sortRestrictionMetaData.GetList(CapabilityConstants.AscendingOnlyProperties)) + : null; } private static SelectionRestriction ParseSelectionRestriction(IDictionary capabilitiesMetaData) { IDictionary selectRestrictionsMetadata = capabilitiesMetaData.GetObject(CapabilityConstants.SelectionRestriction); return selectRestrictionsMetadata == null - ? null - : new SelectionRestriction(selectRestrictionsMetadata.GetBool(CapabilityConstants.Selectable, "selectable property is mandatory and not found.")); + ? null + : new SelectionRestriction(selectRestrictionsMetadata.GetBool(CapabilityConstants.Selectable, "selectable property is mandatory and not found.")); } private static GroupRestriction ParseGroupRestriction(IDictionary capabilitiesMetaData) { IDictionary groupRestrictionMetaData = capabilitiesMetaData.GetObject(CapabilityConstants.GroupRestriction); return groupRestrictionMetaData == null - ? null - : new GroupRestriction(groupRestrictionMetaData.GetList(CapabilityConstants.UngroupableProperties)); + ? null + : new GroupRestriction(groupRestrictionMetaData.GetList(CapabilityConstants.UngroupableProperties)); } internal static string[] ParseFilterFunctions(IDictionary capabilitiesMetaData) diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SortRestriction.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SortRestriction.cs index 1ae3921fb9..993f6d0e44 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SortRestriction.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Capabilities/SortRestriction.cs @@ -15,19 +15,22 @@ internal sealed class SortRestriction [JsonInclude] [JsonPropertyName(CapabilityConstants.AscendingOnlyProperties)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly List AscendingOnlyProperties; + public readonly IList AscendingOnlyProperties; [JsonInclude] [JsonPropertyName(CapabilityConstants.UnsortableProperties)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public readonly List UnsortableProperties; + public readonly IList UnsortableProperties; - public SortRestriction(List unsortableProperties, List ascendingOnlyProperties) + public SortRestriction(IList unsortableProperties, IList ascendingOnlyProperties) { Contracts.AssertValueOrNull(unsortableProperties); Contracts.AssertValueOrNull(ascendingOnlyProperties); + // List of properties which support ascending order only AscendingOnlyProperties = ascendingOnlyProperties; + + // List of unsortable properties UnsortableProperties = unsortableProperties; } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableDescriptor.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableDescriptor.cs deleted file mode 100644 index fedd243c83..0000000000 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableDescriptor.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -namespace Microsoft.PowerFx.Connectors -{ - internal readonly struct CdpTableDescriptor - { - public ConnectorType ConnectorType { get; init; } - - public string Name { get; init; } - - public string DisplayName { get; init; } - - public ServiceCapabilities TableCapabilities { get; init; } - } -} diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs index be8c2712c9..a28c470542 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpTableResolver.cs @@ -8,6 +8,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Types; namespace Microsoft.PowerFx.Connectors @@ -24,9 +25,6 @@ internal class CdpTableResolver : ICdpTableResolver private readonly bool _doubleEncoding; - // Temporary hack to generate ADS - public bool GenerateADS { get; init; } - public CdpTableResolver(CdpTable tabularTable, HttpClient httpClient, string uriPrefix, bool doubleEncoding, ConnectorLogger logger = null) { _tabularTable = tabularTable; @@ -37,7 +35,7 @@ public CdpTableResolver(CdpTable tabularTable, HttpClient httpClient, string uri Logger = logger; } - public async Task ResolveTableAsync(string tableName, CancellationToken cancellationToken) + public async Task ResolveTableAsync(string tableName, CancellationToken cancellationToken) { // out string name, out string displayName, out ServiceCapabilities tableCapabilities cancellationToken.ThrowIfCancellationRequested(); @@ -56,102 +54,69 @@ public async Task ResolveTableAsync(string tableName, Cancel string text = await CdpServiceBase.GetObject(_httpClient, $"Get table metadata", uri, null, cancellationToken, Logger).ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(text)) + if (string.IsNullOrWhiteSpace(text)) { - List sqlRelationships = null; - - // for SQL need to get relationships separately as they aren't included by CDP connector - if (IsSql()) - { - cancellationToken.ThrowIfCancellationRequested(); - - // We can't execute a query like below for unknown reasons so we'll have to do it in retrieving each table's data - // and doing the joins manually (in GetSqlRelationships) - // -- - // SELECT fk.name 'FK Name', tp.name 'Parent table', cp.name, tr.name 'Refrenced table', cr.name - // FROM sys.foreign_keys fk - // INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id - // INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id - // INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id - // INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id - // INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id - // ORDER BY tp.name, cp.column_id - // -- - - uri = (_uriPrefix ?? string.Empty) + $"/v2/datasets/{dataset}/query/sql"; - string body = - @"{""query"":""select name, object_id, parent_object_id, referenced_object_id from sys.foreign_keys; " + - @"select object_id, name from sys.tables; " + - @"select constraint_object_id, parent_column_id, parent_object_id, referenced_column_id, referenced_object_id from sys.foreign_key_columns; " + - @"select name, object_id, column_id from sys.columns""}"; - - string text2 = await CdpServiceBase.GetObject(_httpClient, $"Get SQL relationships", uri, body, cancellationToken, Logger).ConfigureAwait(false); - - // Result should be cached - sqlRelationships = GetSqlRelationships(text2); - - // Filter on ParentTable - string tbl = tableName.Split('.').Last().Replace("[", string.Empty).Replace("]", string.Empty); - sqlRelationships = sqlRelationships.Where(sr => sr.ParentTable == tbl).ToList(); - } + return null; + } - string connectorName = _uriPrefix.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[1]; - ConnectorType ct = ConnectorFunction.GetConnectorTypeAndTableCapabilities(this, connectorName, "Schema/Items", FormulaValue.New(text), sqlRelationships, ConnectorCompatibility.CdpCompatibility, _tabularTable.DatasetName, out string name, out string displayName, out ServiceCapabilities tableCapabilities); + List sqlRelationships = null; - return new CdpTableDescriptor() { ConnectorType = ct, Name = name, DisplayName = displayName, TableCapabilities = tableCapabilities }; + // for SQL need to get relationships separately as they aren't included by CDP connector + if (IsSql()) + { + cancellationToken.ThrowIfCancellationRequested(); + + uri = (_uriPrefix ?? string.Empty) + $"/v2/datasets/{dataset}/query/sql"; + string body = + @"{""query"":""SELECT fk.name AS FK_Name, '[' + sp.name + '].[' + tp.name + ']' AS Parent_Table, cp.name AS Parent_Column, '[' + sr.name + '].[' + tr.name + ']' AS Referenced_Table, cr.name AS Referenced_Column" + + @" FROM sys.foreign_keys fk" + + @" INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id" + + @" INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id" + + @" INNER JOIN sys.schemas sp on tp.schema_id = sp.schema_id" + + @" INNER JOIN sys.schemas sr on tr.schema_id = sr.schema_id" + + @" INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id" + + @" INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id" + + @" INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id" + + @" WHERE '[' + sp.name + '].[' + tp.name + ']' = '" + tableName + "'" + @"""}"; + + string text2 = await CdpServiceBase.GetObject(_httpClient, $"Get SQL relationships", uri, body, cancellationToken, Logger).ConfigureAwait(false); + + // Result should be cached + sqlRelationships = GetSqlRelationships(text2); } - return new CdpTableDescriptor(); + string connectorName = _uriPrefix.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[1]; + + return ConnectorFunction.GetCdpTableType(this, connectorName, "Schema/Items", FormulaValue.New(text), sqlRelationships, ConnectorCompatibility.CdpCompatibility, _tabularTable.DatasetName, out string name, out string displayName, out TableDelegationInfo delegationInfo); } private bool IsSql() => _uriPrefix.Contains("/sql/"); private List GetSqlRelationships(string text) - { - Result r = JsonSerializer.Deserialize(text); - - SqlForeignKey[] fkt = r.ResultSets.Table1; + { + RelationshipResult r = JsonSerializer.Deserialize(text); - if (fkt == null || fkt.Length == 0) + var relationships = r.ResultSets.Table1; + if (relationships == null || relationships.Length == 0) { return new List(); } - SqlTable[] tt = r.ResultSets.Table2; - SqlForeignKeyColumn[] fkct = r.ResultSets.Table3; - SqlColumn[] ct = r.ResultSets.Table4; - List sqlRelationShips = new List(); - foreach (SqlForeignKey fk in fkt) + foreach (var fk in relationships) { - foreach (SqlTable tp in tt.Where(tp => fk.parent_object_id == tp.object_id)) + sqlRelationShips.Add(new SqlRelationship() { - foreach (SqlTable tr in tt.Where(tr => fk.referenced_object_id == tr.object_id)) - { - foreach (SqlForeignKeyColumn fkc in fkct.Where(fkc => fkc.constraint_object_id == fk.object_id)) - { - foreach (SqlColumn cp in ct.Where(cp => fkc.parent_column_id == cp.column_id && fkc.parent_object_id == cp.object_id)) - { - foreach (SqlColumn cr in ct.Where(cr => fkc.referenced_column_id == cr.column_id && fkc.referenced_object_id == cr.object_id)) - { - sqlRelationShips.Add(new SqlRelationship() - { - RelationshipName = fk.name, - ParentTable = tp.name, - ColumnName = cp.name, - ReferencedTable = tr.name, - ReferencedColumnName = cr.name, - ColumnId = cp.column_id - }); - } - } - } - } - } + RelationshipName = fk.FK_Name, + ParentTable = fk.Parent_Table, + ColumnName = fk.Parent_Column, + ReferencedTable = fk.Referenced_Table, + ReferencedColumnName = fk.Referenced_Column + }); } - return sqlRelationShips.OrderBy(sr => sr.ParentTable).ThenBy(sr => sr.ColumnId).ToList(); + return sqlRelationShips; } } @@ -165,52 +130,31 @@ internal class SqlRelationship public string ColumnName; public string ReferencedTable; public string ReferencedColumnName; - public long ColumnId; public override string ToString() => $"{RelationshipName}, {ParentTable}, {ColumnName}, {ReferencedTable}, {ReferencedColumnName}"; } - internal class Result + internal class RelationshipResult { - public ResultSets ResultSets { get; set; } + public RelationshipResultSets ResultSets { get; set; } } - internal class ResultSets + internal class RelationshipResultSets { - public SqlForeignKey[] Table1 { get; set; } - public SqlTable[] Table2 { get; set; } - public SqlForeignKeyColumn[] Table3 { get; set; } - public SqlColumn[] Table4 { get; set; } + public FKRelationship[] Table1 { get; set; } } - internal class SqlForeignKey + internal class FKRelationship { - public string name { get; set; } - public long object_id { get; set; } - public long parent_object_id { get; set; } - public long referenced_object_id { get; set; } - } + public string FK_Name { get; set; } - internal class SqlTable - { - public long object_id { get; set; } - public string name { get; set; } - } + public string Parent_Table { get; set; } - internal class SqlForeignKeyColumn - { - public long constraint_object_id { get; set; } - public long parent_column_id { get; set; } - public long parent_object_id { get; set; } - public long referenced_column_id { get; set; } - public long referenced_object_id { get; set; } - } + public string Parent_Column { get; set; } - internal class SqlColumn - { - public string name { get; set; } - public long object_id { get; set; } - public long column_id { get; set; } + public string Referenced_Table { get; set; } + + public string Referenced_Column { get; set; } } #pragma warning restore SA1516 diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ExternalCdpDataSource.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ExternalCdpDataSource.cs deleted file mode 100644 index 5badccba7c..0000000000 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ExternalCdpDataSource.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using Microsoft.PowerFx.Core.Entities; -using Microsoft.PowerFx.Core.Entities.Delegation; -using Microsoft.PowerFx.Core.Entities.QueryOptions; -using Microsoft.PowerFx.Core.Functions.Delegation; -using Microsoft.PowerFx.Core.Types; -using Microsoft.PowerFx.Core.UtilityDataStructures; -using Microsoft.PowerFx.Core.Utils; - -namespace Microsoft.PowerFx.Connectors -{ - internal class ExternalCdpDataSource : IExternalTabularDataSource - { - public ExternalCdpDataSource(DName name, string datasetName, ServiceCapabilities serviceCapabilities, bool isReadOnly, BidirectionalDictionary displayNameMapping = null) - { - EntityName = name; - ServiceCapabilities = serviceCapabilities; - IsWritable = !isReadOnly; - - CdpEntityMetadataProvider metadataProvider = new CdpEntityMetadataProvider(); - CdpDataSourceMetadata tabularDataSourceMetadata = new CdpDataSourceMetadata(name.Value, datasetName); - tabularDataSourceMetadata.LoadClientSemantics(); - metadataProvider.AddSource(name.Value, tabularDataSourceMetadata); - - DataEntityMetadataProvider = metadataProvider; - IsConvertingDisplayNameMapping = false; - DisplayNameMapping = displayNameMapping ?? new BidirectionalDictionary(); - PreviousDisplayNameMapping = null; - } - - internal ServiceCapabilities ServiceCapabilities; - - public TabularDataQueryOptions QueryOptions => new TabularDataQueryOptions(this); - - public bool HasCachedCountRows => false; - - public string Name => EntityName.Value; - - public bool IsSelectable => ServiceCapabilities.IsSelectable; - - public bool IsDelegatable => ServiceCapabilities.IsDelegable; - - public bool IsPageable => ServiceCapabilities.IsPagable; - - public bool IsClearable => throw new NotImplementedException(); - - public bool IsRefreshable => true; - - public bool RequiresAsync => true; - - public bool IsWritable { get; } - - public IExternalDataEntityMetadataProvider DataEntityMetadataProvider { get; } - - public DataSourceKind Kind => DataSourceKind.Connected; - - public IExternalTableMetadata TableMetadata => null; /* _tableMetadata; */ - - public IDelegationMetadata DelegationMetadata => throw new NotImplementedException(); - - public DName EntityName { get; } - - public DType Type => throw new NotImplementedException(); - - public bool IsConvertingDisplayNameMapping { get; protected set; } - - public BidirectionalDictionary DisplayNameMapping { get; protected set; } - - public BidirectionalDictionary PreviousDisplayNameMapping { get; protected set; } - - public bool CanIncludeExpand(IExpandInfo expandToAdd) => true; - - public bool CanIncludeExpand(IExpandInfo parentExpandInfo, IExpandInfo expandToAdd) => true; - - public bool CanIncludeSelect(string selectColumnName) => TableMetadata != null; /* && TableMetadata.CanIncludeSelect(selectColumnName);*/ - - public bool CanIncludeSelect(IExpandInfo expandInfo, string selectColumnName) => true; - - public IReadOnlyList GetKeyColumns() - { - return /*TableMetadata?.KeyColumns ??*/ new List(); - } - - public IEnumerable GetKeyColumns(IExpandInfo expandInfo) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ICdpTableResolver.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ICdpTableResolver.cs index 889f7f804c..7dece9e03f 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ICdpTableResolver.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/ICdpTableResolver.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; using System.Threading; using System.Threading.Tasks; @@ -11,9 +10,6 @@ internal interface ICdpTableResolver { ConnectorLogger Logger { get; } - [Obsolete("This property is a temporary hack to generate ADS")] - bool GenerateADS { get; init; } - - Task ResolveTableAsync(string tableName, CancellationToken cancellationToken); + Task ResolveTableAsync(string tableName, CancellationToken cancellationToken); } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs index 5c3bd9fb95..6d5f4efe4f 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpDataSource.cs @@ -32,7 +32,7 @@ public static async Task GetDatasetsMetadataAsync(HttpClient ht return await GetObject(httpClient, "Get datasets metadata", uri, null, cancellationToken, logger).ConfigureAwait(false); } - public async Task> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null) + public virtual async Task> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null) { if (DatasetMetadata == null) { @@ -47,7 +47,7 @@ public async Task> GetTablesAsync(HttpClient httpClient, s + (uriPrefix.Contains("/sharepointonline/") ? "/alltables" : "/tables"); GetTables tables = await GetObject(httpClient, "Get tables", uri, null, cancellationToken, logger).ConfigureAwait(false); - return tables?.Value?.Select(rt => new CdpTable(DatasetName, rt.Name, DatasetMetadata, tables?.Value) { DisplayName = rt.DisplayName }); + return tables?.Value?.Select((RawTable rawTable) => new CdpTable(DatasetName, rawTable.Name, DatasetMetadata, tables?.Value) { DisplayName = rawTable.DisplayName }); } /// @@ -61,7 +61,7 @@ public async Task> GetTablesAsync(HttpClient httpClient, s /// Logger. /// CdpTable class. /// When no or more than one tables are identified. - public async Task GetTableAsync(HttpClient httpClient, string uriPrefix, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null) + public virtual async Task GetTableAsync(HttpClient httpClient, string uriPrefix, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null) { cancellationToken.ThrowIfCancellationRequested(); IEnumerable tables = await GetTablesAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false); diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpService.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpService.cs index a042057653..d45890cc04 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpService.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpService.cs @@ -14,30 +14,25 @@ public abstract class CdpService : CdpServiceBase { private const string NotInitialized = "Tabular service is not initialized."; - public TableType TableType => TabularRecordType?.ToTable(); + public TableType TableType => RecordType?.ToTable(); - public RecordType TabularRecordType { get; private set; } = null; + public RecordType RecordType { get; protected internal set; } = null; public bool IsInitialized => TableType != null; public abstract bool IsDelegable { get; } - public abstract ConnectorType ConnectorType { get; } + internal abstract IReadOnlyDictionary Relationships { get; } public abstract HttpClient HttpClient { get; } public virtual CdpTableValue GetTableValue() { return IsInitialized - ? new CdpTableValue(this, ConnectorType) + ? new CdpTableValue(this, Relationships) : throw new InvalidOperationException(NotInitialized); } - protected void SetRecordType(RecordType recordType) - { - TabularRecordType = recordType; - } - // TABLE METADATA SERVICE // GET: /$metadata.json/datasets/{datasetName}/tables/{tableName}?api-version=2015-09-01 // Implemented in InitAsync() in derived classes diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs index 3f29e620e2..522ba7c734 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs +++ b/src/libraries/Microsoft.PowerFx.Connectors/Tabular/Services/CdpTable.cs @@ -8,13 +8,14 @@ using System.Threading; using System.Threading.Tasks; using System.Web; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.IR; using Microsoft.PowerFx.Types; namespace Microsoft.PowerFx.Connectors { // Implements CDP protocol for Tabular connectors - public sealed class CdpTable : CdpService + public class CdpTable : CdpService { public string TableName { get; private set; } @@ -24,15 +25,15 @@ public sealed class CdpTable : CdpService public override HttpClient HttpClient => _httpClient; - public override bool IsDelegable => TableCapabilities?.IsDelegable ?? false; + public override bool IsDelegable => (DelegationInfo?.SortRestriction != null) || (DelegationInfo?.FilterRestriction != null) || (DelegationInfo?.FilterFunctions != null); - public override ConnectorType ConnectorType => TabularTableDescriptor.ConnectorType; + internal TableDelegationInfo DelegationInfo => ((DataSourceInfo)TabularTableDescriptor.FormulaType._type.AssociatedDataSources.First()).DelegationInfo; - internal ServiceCapabilities TableCapabilities => TabularTableDescriptor.TableCapabilities; + internal override IReadOnlyDictionary Relationships => _relationships; internal DatasetMetadata DatasetMetadata; - internal CdpTableDescriptor TabularTableDescriptor; + internal ConnectorType TabularTableDescriptor; internal IReadOnlyCollection Tables; @@ -40,6 +41,8 @@ public sealed class CdpTable : CdpService private HttpClient _httpClient; + private IReadOnlyDictionary _relationships; + internal CdpTable(string dataset, string table, IReadOnlyCollection tables) { DatasetName = dataset ?? throw new ArgumentNullException(nameof(dataset)); @@ -55,7 +58,7 @@ internal CdpTable(string dataset, string table, DatasetMetadata datasetMetadata, //// TABLE METADATA SERVICE // GET: /$metadata.json/datasets/{datasetName}/tables/{tableName}?api-version=2015-09-01 - public async Task InitAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null) + public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null) { cancellationToken.ThrowIfCancellationRequested(); @@ -66,14 +69,6 @@ public async Task InitAsync(HttpClient httpClient, string uriPrefix, Cancellatio _httpClient = httpClient; - // $$$ This is a hack to generate ADS - bool adsHack = false; - if (uriPrefix.StartsWith("*", StringComparison.Ordinal)) - { - adsHack = true; - uriPrefix = uriPrefix.Substring(1); - } - if (DatasetMetadata == null) { await InitializeDatasetMetadata(httpClient, uriPrefix, logger, cancellationToken).ConfigureAwait(false); @@ -81,10 +76,12 @@ public async Task InitAsync(HttpClient httpClient, string uriPrefix, Cancellatio _uriPrefix = uriPrefix; - CdpTableResolver tableResolver = new CdpTableResolver(this, httpClient, uriPrefix, DatasetMetadata.IsDoubleEncoding, logger) { GenerateADS = adsHack }; + CdpTableResolver tableResolver = new CdpTableResolver(this, httpClient, uriPrefix, DatasetMetadata.IsDoubleEncoding, logger); TabularTableDescriptor = await tableResolver.ResolveTableAsync(TableName, cancellationToken).ConfigureAwait(false); - SetRecordType((RecordType)TabularTableDescriptor.ConnectorType?.FormulaType); + _relationships = TabularTableDescriptor.Relationships; + + RecordType = (RecordType)TabularTableDescriptor.FormulaType; } private async Task InitializeDatasetMetadata(HttpClient httpClient, string uriPrefix, ConnectorLogger logger, CancellationToken cancellationToken) diff --git a/src/libraries/Microsoft.PowerFx.Core/AssemblyProperties.cs b/src/libraries/Microsoft.PowerFx.Core/AssemblyProperties.cs index 5b2f400237..ad4afdc247 100644 --- a/src/libraries/Microsoft.PowerFx.Core/AssemblyProperties.cs +++ b/src/libraries/Microsoft.PowerFx.Core/AssemblyProperties.cs @@ -44,6 +44,7 @@ [assembly: InternalsVisibleTo("Microsoft.PowerFx.Connectors, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.PowerFx.LanguageServerProtocol, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.PowerFx.Json, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("Microsoft.PowerFx.Repl, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.PowerFx.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.PowerFx.Interpreter.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("Microsoft.PowerFx.Json.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs index 12a9732608..c04f9e1c99 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Binding/Binder.cs @@ -1002,7 +1002,7 @@ internal IEnumerable GetDataQuerySelects(TexlNode node) return Enumerable.Empty(); } - var ruleQueryOptions = Rule.Binding.QueryOptions.GetQueryOptions(ds); + var ruleQueryOptions = Rule.Binding?.QueryOptions.GetQueryOptions(ds); if (ruleQueryOptions != null) { foreach (var nodeQO in Rule.TexlNodeQueryOptions) @@ -2940,7 +2940,13 @@ public override void Visit(FirstNameNode node) _txb.SetSetMutable(node, nameSymbol?.Props.CanSetMutate ?? false); if (lookupInfo.Data is IExternalNamedFormula formula) { - isConstantNamedFormula = formula.IsConstant; + isConstantNamedFormula = formula.IsConstant; + + // If the definition of the named formula has a delegation warning, every use should also inherit this warning + if (formula.HasDelegationWarning) + { + _txb.ErrorContainer.EnsureError(DocumentErrorSeverity.Warning, node, TexlStrings.SuggestRemoteExecutionHint_NF, node.Ident.Name); + } } } else if (lookupInfo.Kind == BindKind.Data) @@ -3612,7 +3618,7 @@ public override void PostVisit(DottedNameNode node) // If the reference is to Control.Property and the rule for that Property is a constant, // we need to mark the node as constant, and save the control info so we may look up the // rule later. - if (controlInfo?.GetRule(property.InvariantName) is { HasErrorsOrWarnings: false } rule && rule.Binding.IsConstant(rule.Binding.Top)) + if (controlInfo?.GetRule(property.InvariantName) is IExternalRule rule && rule.Binding != null && !rule.HasErrorsOrWarnings && rule.Binding.IsConstant(rule.Binding.Top)) { value = controlInfo; isConstant = true; @@ -4157,15 +4163,14 @@ private void SetCallNodePurity(CallNode node, CallInfo info) var infoTexlFunction = info.Function; if (_txb._glue.IsComponentScopedPropertyFunction(infoTexlFunction)) - { - // We only have to check the property's rule and the calling arguments for purity as scoped variables - // (default values) are by definition data rules and therefore always pure. - if (_txb.Document != null && _txb.Document.TryGetControlByUniqueId(infoTexlFunction.Namespace.Name.Value, out var ctrl) && - ctrl.TryGetRule(new DName(infoTexlFunction.Name), out var rule)) - { - hasSideEffects |= rule.Binding.HasSideEffects(rule.Binding.Top); - isStateFul |= rule.Binding.IsStateful(rule.Binding.Top); - } + { + // Behavior only component properties should be treated as stateful. + hasSideEffects |= infoTexlFunction.IsBehaviorOnly; + + // At the moment, we're going to treat all invocations of component scoped property functions as stateful. + // This ensures that we don't lift these function invocations in loops, and that they are re-evaluated every time they are called, + // which is always correct, although less efficient in some cases. + isStateFul |= true; } else { @@ -4827,8 +4832,15 @@ private void FinalizeCall(CallNode node) // Invalid datasources always result in error if (func.IsBehaviorOnly && !_txb.BindingConfig.AllowsSideEffects) - { - _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrBehaviorPropertyExpected); + { + if (_txb.BindingConfig.UserDefinitionsMode) + { + _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrBehaviorFunctionInDataUDF); + } + else + { + _txb.ErrorContainer.EnsureError(node, TexlStrings.ErrBehaviorPropertyExpected); + } } // Test-only functions can only be used within test cases. @@ -4865,6 +4877,12 @@ private void FinalizeCall(CallNode node) { _txb.ErrorContainer.EnsureError(node, errorKey, badAncestor.Head.Name); } + } + + // If the definition of the user-defined function has a delegation warning, every usage should also inherit this warning + if (func is UserDefinedFunction udf && udf.HasDelegationWarning) + { + _txb.ErrorContainer.EnsureError(DocumentErrorSeverity.Warning, node, TexlStrings.SuggestRemoteExecutionHint_UDF, udf.Name); } _txb.CheckAndMarkAsDelegatable(node); @@ -5141,6 +5159,14 @@ private void PreVisitTypeArgAndProccesCallNode(CallNode node, TexlFunction func) return; } + if (!_features.IsUserDefinedTypesEnabled) + { + _txb.ErrorContainer.Error(node, TexlStrings.ErrUserDefinedTypesDisabled); + _txb.SetInfo(node, new CallInfo(func, node)); + _txb.SetType(node, DType.Error); + return; + } + Contracts.Assert(argCount > 1); Contracts.AssertValue(args[1]); diff --git a/src/libraries/Microsoft.PowerFx.Core/Binding/BinderUtils.cs b/src/libraries/Microsoft.PowerFx.Core/Binding/BinderUtils.cs index 29a5f44006..b9027200df 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Binding/BinderUtils.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Binding/BinderUtils.cs @@ -1563,18 +1563,9 @@ public static bool TryGetConstantValue(CheckTypesContext context, TexlNode node, // Possibly an enumeration var dottedNameNode = node.AsDottedName(); if (dottedNameNode.Left.Kind == NodeKind.FirstName) - { - // Strongly-typed enums - if (context.NameResolver.Lookup(dottedNameNode.Left.AsFirstName().Ident.Name, out NameLookupInfo nameInfo) && nameInfo.Kind == BindKind.Enum) - { - if (nameInfo.Data is EnumSymbol enumSymbol && enumSymbol.TryGetValue(dottedNameNode.Right.Name, out OptionSetValue osv)) - { - nodeValue = osv.ToObject().ToString(); - return true; - } - } - - // With strongly-typed enums disabled + { + // If the entity scope exists, look up from there. + // Once PA Client impls Strongly Typed enums, this may need to update. DType enumType = DType.Invalid; if (context.NameResolver.EntityScope?.TryGetNamedEnum(dottedNameNode.Left.AsFirstName().Ident.Name, out enumType) ?? false) { @@ -1586,6 +1577,16 @@ public static bool TryGetConstantValue(CheckTypesContext context, TexlNode node, return true; } } + } + + // Strongly-typed enums + if (context.NameResolver.Lookup(dottedNameNode.Left.AsFirstName().Ident.Name, out NameLookupInfo nameInfo, NameLookupPreferences.GlobalsOnly) && nameInfo.Kind == BindKind.Enum) + { + if (nameInfo.Data is EnumSymbol enumSymbol && enumSymbol.TryGetValue(dottedNameNode.Right.Name, out OptionSetValue osv)) + { + nodeValue = osv.ToObject().ToString(); + return true; + } } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Binding/BindingConfig.cs b/src/libraries/Microsoft.PowerFx.Core/Binding/BindingConfig.cs index 17a4679c7b..d8824d6397 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Binding/BindingConfig.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Binding/BindingConfig.cs @@ -23,15 +23,18 @@ internal class BindingConfig public bool AnalysisMode { get; } - public bool MarkAsAsyncOnLazilyLoadedControlRef { get; } = false; + public bool MarkAsAsyncOnLazilyLoadedControlRef { get; } = false; + + public bool UserDefinitionsMode { get; } - public BindingConfig(bool allowsSideEffects = false, bool useThisRecordForRuleScope = false, bool numberIsFloat = false, bool analysisMode = false, bool markAsAsyncOnLazilyLoadedControlRef = false) + public BindingConfig(bool allowsSideEffects = false, bool useThisRecordForRuleScope = false, bool numberIsFloat = false, bool analysisMode = false, bool markAsAsyncOnLazilyLoadedControlRef = false, bool userDefinitionsMode = false) { AllowsSideEffects = allowsSideEffects; UseThisRecordForRuleScope = useThisRecordForRuleScope; NumberIsFloat = numberIsFloat; AnalysisMode = analysisMode; - MarkAsAsyncOnLazilyLoadedControlRef = markAsAsyncOnLazilyLoadedControlRef; + MarkAsAsyncOnLazilyLoadedControlRef = markAsAsyncOnLazilyLoadedControlRef; + UserDefinitionsMode = userDefinitionsMode; } } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Entities/External/DataSourceInfo.cs b/src/libraries/Microsoft.PowerFx.Core/Entities/External/DataSourceInfo.cs new file mode 100644 index 0000000000..ef1e6a40d4 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Core/Entities/External/DataSourceInfo.cs @@ -0,0 +1,216 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.PowerFx.Core.Entities.Delegation; +using Microsoft.PowerFx.Core.Entities.QueryOptions; +using Microsoft.PowerFx.Core.Functions.Delegation; +using Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata; +using Microsoft.PowerFx.Core.Types; +using Microsoft.PowerFx.Core.UtilityDataStructures; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Types; + +namespace Microsoft.PowerFx.Core.Entities +{ + // Implements a base data source, used in DType.AssociatedDataSources, itself in RecordType constructor to host a CDP record type + internal class DataSourceInfo : IExternalTabularDataSource + { + // Key = field logical name, Value = foreign table logical name + public readonly IReadOnlyDictionary ColumnsWithRelationships; + + public DataSourceInfo(AggregateType recordType, DisplayNameProvider displayNameProvider, TableDelegationInfo delegationInfo) + { + EntityName = new DName(delegationInfo.TableName); + IsWritable = !delegationInfo.IsReadOnly; + _delegationInfo = delegationInfo; + _type = recordType._type; + RecordType = (RecordType)recordType; + + _displayNameMapping = new BidirectionalDictionary(displayNameProvider.LogicalToDisplayPairs.ToDictionary(kvp => kvp.Key.Value, kvp => kvp.Value.Value ?? kvp.Key.Value)); + _externalDataEntityMetadataProvider = new InternalDataEntityMetadataProvider(); + _externalDataEntityMetadataProvider.AddSource(Name, new InternalDataEntityMetadata(delegationInfo.TableName, delegationInfo.DatasetName, _displayNameMapping)); + _externalTableMetadata = new InternalTableMetadata(RecordType, Name, Name, delegationInfo.IsReadOnly); + _delegationMetadata = new DelegationMetadataBase(_type, new CompositeCapabilityMetadata(_type, GetCapabilityMetadata(recordType, delegationInfo))); + _tabularDataQueryOptions = new TabularDataQueryOptions(this); + _previousDisplayNameMapping = null; + + ColumnsWithRelationships = delegationInfo.ColumnsWithRelationships; + } + + public TableDelegationInfo DelegationInfo => _delegationInfo; + + private readonly TableDelegationInfo _delegationInfo; + + private readonly DType _type; + + private readonly InternalDataEntityMetadataProvider _externalDataEntityMetadataProvider; + + private readonly InternalTableMetadata _externalTableMetadata; + + private readonly DelegationMetadataBase _delegationMetadata; + + private readonly TabularDataQueryOptions _tabularDataQueryOptions; + + private readonly BidirectionalDictionary _displayNameMapping; + + private readonly BidirectionalDictionary _previousDisplayNameMapping; + + TabularDataQueryOptions IExternalTabularDataSource.QueryOptions => _tabularDataQueryOptions; + + public string Name => EntityName.Value; + + public DName EntityName { get; } + + public bool IsSelectable => _delegationInfo.IsSelectable; + + public bool IsDelegatable => _delegationInfo.IsDelegable; + + public bool IsRefreshable => true; + + public bool RequiresAsync => true; + + public bool IsWritable { get; } + + public bool IsClearable => throw new System.NotImplementedException(); + + IExternalDataEntityMetadataProvider IExternalDataSource.DataEntityMetadataProvider => _externalDataEntityMetadataProvider; + + DataSourceKind IExternalDataSource.Kind => DataSourceKind.Connected; + + IExternalTableMetadata IExternalDataSource.TableMetadata => _externalTableMetadata; + + IDelegationMetadata IExternalDataSource.DelegationMetadata => _delegationMetadata; + + DType IExternalEntity.Type => _type; + + public RecordType RecordType { get; } + + public bool IsPageable => _delegationInfo.PagingCapabilities.IsOnlyServerPagable || IsDelegatable; + + public bool IsConvertingDisplayNameMapping => false; + + BidirectionalDictionary IDisplayMapped.DisplayNameMapping => _displayNameMapping; + + BidirectionalDictionary IDisplayMapped.PreviousDisplayNameMapping => _previousDisplayNameMapping; + + public bool HasCachedCountRows => false; + + public IReadOnlyList GetKeyColumns() => _externalTableMetadata?.KeyColumns ?? new List(); + + IEnumerable IExternalTabularDataSource.GetKeyColumns(IExpandInfo expandInfo) + { + throw new NotImplementedException(); + } + + public bool CanIncludeSelect(string selectColumnName) => _externalTableMetadata != null && _externalTableMetadata.CanIncludeSelect(selectColumnName); + + bool IExternalTabularDataSource.CanIncludeSelect(IExpandInfo expandInfo, string selectColumnName) + { + throw new NotImplementedException(); + } + + bool IExternalTabularDataSource.CanIncludeExpand(IExpandInfo expandToAdd) + { + throw new NotImplementedException(); + } + + bool IExternalTabularDataSource.CanIncludeExpand(IExpandInfo parentExpandInfo, IExpandInfo expandToAdd) + { + throw new NotImplementedException(); + } + + private static List GetCapabilityMetadata(AggregateType recordType, TableDelegationInfo delegationInfo) + { + DType type = recordType._type; + + DPath GetDPath(string prop) => DPath.Root.Append(new DName(prop)); + + void AddOrUpdate(Dictionary dic, string prop, DelegationCapability capability) + { + DPath dPath = GetDPath(prop); + + if (!dic.TryGetValue(dPath, out DelegationCapability existingCapability)) + { + dic.Add(dPath, capability); + } + else + { + dic[dPath] = new DelegationCapability(existingCapability.Capabilities | capability.Capabilities); + } + } + + Dictionary groupByRestrictions = new Dictionary(); + + if (delegationInfo?.GroupRestriction?.UngroupableProperties != null) + { + foreach (string ungroupableProperty in delegationInfo.GroupRestriction.UngroupableProperties) + { + AddOrUpdate(groupByRestrictions, ungroupableProperty, DelegationCapability.Group); + } + } + + Dictionary oDataReplacements = new Dictionary(); + + FilterOpMetadata filterOpMetadata = new CdpFilterOpMetadata(recordType, delegationInfo); + GroupOpMetadata groupOpMetadata = new GroupOpMetadata(type, groupByRestrictions); + ODataOpMetadata oDataOpMetadata = new ODataOpMetadata(type, oDataReplacements); + + List metadataList = new List() + { + filterOpMetadata, + groupOpMetadata, + oDataOpMetadata + }; + + if (delegationInfo?.SortRestriction != null) + { + Dictionary sortRestrictions = new Dictionary(); + + if (delegationInfo?.SortRestriction?.UnsortableProperties != null) + { + foreach (string unsortableProperty in delegationInfo.SortRestriction.UnsortableProperties) + { + AddOrUpdate(sortRestrictions, unsortableProperty, DelegationCapability.Sort); + } + } + + if (delegationInfo?.SortRestriction?.AscendingOnlyProperties != null) + { + foreach (string ascendingOnlyProperty in delegationInfo.SortRestriction.AscendingOnlyProperties) + { + AddOrUpdate(sortRestrictions, ascendingOnlyProperty, DelegationCapability.SortAscendingOnly); + } + } + + SortOpMetadata sortOpMetadata = new SortOpMetadata(type, sortRestrictions); + + metadataList.Add(sortOpMetadata); + } + + return metadataList; + } + + internal static DPath GetReplacementPath(string alias, DPath currentColumnPath) + { + if (alias.Contains("/")) + { + var fullPath = DPath.Root; + + foreach (var name in alias.Split('/')) + { + fullPath = fullPath.Append(new DName(name)); + } + + return fullPath; + } + else + { + // Task 5593666: This is temporary to not cause regressions while sharepoint switches to using full query param + return currentColumnPath.Append(new DName(alias)); + } + } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalNamedFormula.cs b/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalNamedFormula.cs index a35cf95ebb..880b0f44ee 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalNamedFormula.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Entities/External/IExternalNamedFormula.cs @@ -13,6 +13,8 @@ internal interface IExternalNamedFormula : IExternalPageableSymbol, IExternalDel bool IsConstant { get; } - bool ContainsReferenceToView { get; } + bool ContainsReferenceToView { get; } + + bool HasDelegationWarning { get; } } } diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpDataSourceMetadata.cs b/src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalDataEntityMetadata.cs similarity index 89% rename from src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpDataSourceMetadata.cs rename to src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalDataEntityMetadata.cs index f0a8def2bb..011b430d78 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpDataSourceMetadata.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalDataEntityMetadata.cs @@ -7,9 +7,9 @@ using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Core.UtilityDataStructures; -namespace Microsoft.PowerFx.Connectors +namespace Microsoft.PowerFx.Core.Entities { - internal class CdpDataSourceMetadata : IDataEntityMetadata + internal class InternalDataEntityMetadata : IDataEntityMetadata { public string EntityName { get; private set; } @@ -33,7 +33,7 @@ internal class CdpDataSourceMetadata : IDataEntityMetadata public IExternalTableDefinition EntityDefinition => throw new NotImplementedException(); - public CdpDataSourceMetadata(string entityName, string datasetName, BidirectionalDictionary displayNameMapping = null) + public InternalDataEntityMetadata(string entityName, string datasetName, BidirectionalDictionary displayNameMapping = null) { EntityName = entityName; DatasetName = datasetName; diff --git a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpEntityMetadataProvider.cs b/src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalDataEntityMetadataProvider.cs similarity index 80% rename from src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpEntityMetadataProvider.cs rename to src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalDataEntityMetadataProvider.cs index 4580b0f733..6325f920f8 100644 --- a/src/libraries/Microsoft.PowerFx.Connectors/Tabular/CdpEntityMetadataProvider.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalDataEntityMetadataProvider.cs @@ -5,19 +5,19 @@ using Microsoft.PowerFx.Core.Entities.Delegation; using Microsoft.PowerFx.Core.Functions.Delegation; -namespace Microsoft.PowerFx.Connectors +namespace Microsoft.PowerFx.Core.Entities { - internal class CdpEntityMetadataProvider : IExternalDataEntityMetadataProvider + internal class InternalDataEntityMetadataProvider : IExternalDataEntityMetadataProvider { private readonly Dictionary _entityIdToMetadataMap; - public CdpEntityMetadataProvider() + public InternalDataEntityMetadataProvider() { _entityIdToMetadataMap = new Dictionary(); } - + public void AddSource(string sourceName, IDataEntityMetadata tabularDataSource) - { + { _entityIdToMetadataMap.Add(sourceName, tabularDataSource); } diff --git a/src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalTableMetadata.cs b/src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalTableMetadata.cs new file mode 100644 index 0000000000..98224aaa93 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalTableMetadata.cs @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.PowerFx.Core.App; +using Microsoft.PowerFx.Core.Types; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Types; + +namespace Microsoft.PowerFx.Core.Entities +{ + internal class InternalTableMetadata : IExternalTableMetadata + { + private readonly RecordType _type; + + public InternalTableMetadata(RecordType recordType, string name, string displayName, bool isReadOnly, string parameterPkColumnName = "") + { + Contracts.AssertNonEmpty(name); + Contracts.AssertNonEmpty(displayName); + + _type = recordType; + + Name = name; + DisplayName = displayName; + IsReadOnly = isReadOnly; + + ParameterPkColumnName = parameterPkColumnName; + } + + public string Name { get; } + + public string DisplayName { get; } + + public string ParameterPkColumnName { get; } + + public bool IsReadOnly { get; } + + public IReadOnlyList KeyColumns => _type._type.DisplayNameProvider.LogicalToDisplayPairs.Select(pair => pair.Key.Value).Where(col => col != "57dfb1b5-7d79-4046-a4da-fd831d5befe1-KeyId").ToList(); + + public IReadOnlyList Columns { get; } + + public ColumnMetadata this[string columnName] + { + get + { + Contracts.AssertNonEmpty(columnName); + return TryGetColumn(columnName, out ColumnMetadata columnMetadata) ? columnMetadata : null; + } + } + + public bool TryGetColumn(string columnName, out ColumnMetadata column) + { + string GetDisplayName(string fieldName) => _type._type.DisplayNameProvider == null || !_type._type.DisplayNameProvider.TryGetDisplayName(new DName(fieldName), out DName displayName) ? fieldName : displayName.Value; + DataFormat? ToDataFormat(DType dType) => dType.Kind switch + { + DKind.Record or DKind.Table or DKind.OptionSetValue => DataFormat.Lookup, + DKind.String or DKind.Decimal or DKind.Number or DKind.Currency => DataFormat.AllowedValues, + _ => null + }; + + Contracts.AssertNonEmpty(columnName); + + if (_type.TryGetUnderlyingFieldType(columnName, out FormulaType ft)) + { + column = new ColumnMetadata( + columnName, + ft._type, + ToDataFormat(ft._type), + GetDisplayName(columnName), + false, // is read-only + false, // primary key + false, // isRequired + ColumnCreationKind.UserProvided, + ColumnVisibility.Default, + columnName, + columnName, + columnName, + null, // columnLookupMetadata + null); // attachmentMetadata + + return true; + } + + column = null; + return false; + } + + /// + /// Checks whether specified column can be included in select query option. + /// + /// + internal bool CanIncludeSelect(string selectColumnName) + { + DType colType = DType.Unknown; + bool hasColumn = TryGetColumn(selectColumnName, out ColumnMetadata columnMetadata); + + if (hasColumn) + { + colType = columnMetadata.Type; + } + + return hasColumn && !colType.IsAttachment; + } + + /// + /// Checks whether specified navigation column can be included in expand query option. + /// + /// + internal bool CanIncludeExpand(IExpandInfo expand) + { + string fieldName = expand.PolymorphicParent ?? expand.Name; + + DType colType = DType.Unknown; + bool hasColumn = TryGetColumn(fieldName, out ColumnMetadata columnMetadata); + + if (hasColumn) + { + colType = columnMetadata.Type; + } + + return hasColumn && (colType.Kind == DKind.Record || colType.Kind == DKind.DataEntity || (colType.Kind == DKind.Polymorphic && ValidatePolymorphicExpand(expand, colType))); + } + + private bool ValidatePolymorphicExpand(IExpandInfo expand, DType colType) + { + if (!colType.HasPolymorphicInfo) + { + return false; + } + + if (colType.PolymorphicInfo.TargetFields.Contains(expand.Name)) + { + return true; + } + + // Owner types have different metadata compared to other polymorphics, + // they use the same field for patching but have different ones for relationships + // which are always hard-coded to 'owning'. + return colType.PolymorphicInfo.TargetFields.Contains("ownerid") && (expand.Name == "owninguser" || expand.Name == "owningteam"); + } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Core/Entities/External/TableDelegationInfo.cs b/src/libraries/Microsoft.PowerFx.Core/Entities/External/TableDelegationInfo.cs new file mode 100644 index 0000000000..4811feed90 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Core/Entities/External/TableDelegationInfo.cs @@ -0,0 +1,298 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.PowerFx.Core.Functions.Delegation; +using Microsoft.PowerFx.Core.Utils; + +namespace Microsoft.PowerFx.Core.Entities +{ + // Supports delegation information for CDP connectors + public abstract class TableDelegationInfo + { + // Defines unsortable columns or columns only supporting ascending ordering + // If set to null, the table is not sortable + public SortRestrictions SortRestriction { get; init; } + + // Defines columns that cannot be sorted and required properties + public FilterRestrictions FilterRestriction { get; init; } + + // Used to indicate whether this table has selectable columns + public SelectionRestrictions SelectionRestriction { get; init; } + + // Defines ungroupable columns + public GroupRestrictions GroupRestriction { get; init; } + + // Filter functions supported by all columns of the table + public IEnumerable FilterFunctions { get; init; } + + // Filter functions supported by the table + public IEnumerable FilterSupportedFunctions { get; init; } + + // Defines paging capabilities + internal PagingCapabilities PagingCapabilities { get; init; } + + // Defining per column capabilities + internal IReadOnlyDictionary ColumnsCapabilities { get; init; } + + // Supports per record permission + internal bool SupportsRecordPermission { get; init; } + + // Logical name of table + public string TableName { get; init; } + + // Read-Only table + public bool IsReadOnly { get; init; } + + // Defines when the table is sortable + public bool IsSortable => SortRestriction != null; + + // Defines when columns can be selected + public bool IsSelectable => SelectionRestriction != null && SelectionRestriction.IsSelectable; + + // Dataset name + public string DatasetName { get; init; } + + // Defines columns with relationships + // Key = field logical name, Value = foreign table logical name + internal Dictionary ColumnsWithRelationships { get; init; } + + public virtual bool IsDelegable => IsSortable || (FilterRestriction != null) || (FilterFunctions != null); + + public TableDelegationInfo() + { + PagingCapabilities = new PagingCapabilities() + { + IsOnlyServerPagable = false, + ServerPagingOptions = new string[0] + }; + SupportsRecordPermission = true; + ColumnsWithRelationships = new Dictionary(); + } + + public abstract ColumnCapabilitiesDefinition GetColumnCapability(string fieldName); + } + + internal sealed class ComplexColumnCapabilities : ColumnCapabilitiesBase + { + internal Dictionary _childColumnsCapabilities; + + public ComplexColumnCapabilities() + { + _childColumnsCapabilities = new Dictionary(); + } + + public void AddColumnCapability(string name, ColumnCapabilitiesBase capability) + { + Contracts.AssertNonEmpty(name); + Contracts.AssertValue(capability); + + _childColumnsCapabilities.Add(name, capability); + } + } + + public sealed class ColumnCapabilities : ColumnCapabilitiesBase + { + public IReadOnlyDictionary Properties => _childColumnsCapabilities.Any() ? _childColumnsCapabilities : null; + + private Dictionary _childColumnsCapabilities; + + private ColumnCapabilitiesDefinition _capabilities; + + public ColumnCapabilitiesDefinition Definition => _capabilities; + + // Those are default CDS filter supported functions + // From // PowerApps-Client\src\Language\PowerFx.Dataverse.Parser\Importers\DataDescription\CdsCapabilities.cs + public static readonly IEnumerable DefaultFilterFunctionSupport = new DelegationOperator[] + { + DelegationOperator.And, + DelegationOperator.Arraylookup, + DelegationOperator.Astype, + DelegationOperator.Average, + DelegationOperator.Cdsin, + DelegationOperator.Contains, + DelegationOperator.Count, + DelegationOperator.Countdistinct, + DelegationOperator.Endswith, + DelegationOperator.Eq, + DelegationOperator.Ge, + DelegationOperator.Gt, + DelegationOperator.Le, + DelegationOperator.Lt, + DelegationOperator.Max, + DelegationOperator.Min, + DelegationOperator.Ne, + DelegationOperator.Not, + DelegationOperator.Null, + DelegationOperator.Or, + DelegationOperator.Startswith, + DelegationOperator.Sum, + DelegationOperator.Top + }; + + public static ColumnCapabilities DefaultColumnCapabilities => new ColumnCapabilities() + { + _capabilities = new ColumnCapabilitiesDefinition() + { + FilterFunctions = DefaultFilterFunctionSupport, + QueryAlias = null, + IsChoice = null + }, + _childColumnsCapabilities = new Dictionary() + }; + + private ColumnCapabilities() + { + } + + public void AddColumnCapability(string name, ColumnCapabilitiesBase capability) + { + Contracts.AssertNonEmpty(name); + Contracts.AssertValue(capability); + + _childColumnsCapabilities.Add(name, capability); + } + + public ColumnCapabilities(ColumnCapabilitiesDefinition capability) + { + Contracts.AssertValueOrNull(capability); + + _capabilities = capability; + _childColumnsCapabilities = new Dictionary(); + } + } + + public sealed class ColumnCapabilitiesDefinition + { + public IEnumerable FilterFunctions + { + get => _filterFunctions ?? ColumnCapabilities.DefaultFilterFunctionSupport; + init => _filterFunctions = value; + } + + // Used in PowerApps-Client/src/AppMagic/js/Core/Core.Data/ConnectedDataDeserialization/TabularDataDeserialization.ts + // Used by SP connector only to rename column logical names in OData queries + internal string QueryAlias { get; init; } + + // Sharepoint delegation specific + internal bool? IsChoice { get; init; } + + private IEnumerable _filterFunctions; + + public ColumnCapabilitiesDefinition() + { + } + + internal DelegationCapability ToDelegationCapability() + { + DelegationCapability columnDelegationCapability = DelegationCapability.None; + + foreach (DelegationOperator columnFilterFunctionEnum in FilterFunctions) + { + string columnFilterFunction = columnFilterFunctionEnum.ToString().ToLowerInvariant(); + + if (DelegationCapability.OperatorToDelegationCapabilityMap.TryGetValue(columnFilterFunction, out DelegationCapability filterFunctionCapability)) + { + columnDelegationCapability |= filterFunctionCapability; + } + } + + columnDelegationCapability |= DelegationCapability.Filter; + + return columnDelegationCapability; + } + } + + public abstract class ColumnCapabilitiesBase + { + } + + internal sealed class PagingCapabilities + { + // Defines is the tabular connector is supporting client or server paging + // If true, @odata.nextlink URL is used instead of $skip and $top query parameters + // If false, $top and $skip will be used + public bool IsOnlyServerPagable { get; init; } + + // Only supported values "top" and "skiptoken" + // Used to define paging options to use + public IEnumerable ServerPagingOptions { get; init; } + + public PagingCapabilities() + { + } + + public PagingCapabilities(bool isOnlyServerPagable, string[] serverPagingOptions) + { + // Server paging restrictions, true for CDS + // Setting 'IsOnlyServerPagable' to true in the table metadata response lets PowerApps application to use + // @odata.nextlink URL in reponse message (instead of $skip and $top query parameters) for page traversal. + // It is also required to set sortable and filterable restrictions for PowerApps to page through results. + IsOnlyServerPagable = isOnlyServerPagable; + + // List of supported server-driven paging capabilities, null for CDS + // ex: top, skiptoken + // used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/AppMagic/js/AppMagic.Services/ConnectedData/CdpConnector.ts&_a=contents&version=GBmaster + ServerPagingOptions = serverPagingOptions; + } + } + + public sealed class GroupRestrictions + { + // Defines properties can cannot be grouped + public IList UngroupableProperties { get; init; } + + public GroupRestrictions() + { + } + } + + public sealed class SelectionRestrictions + { + // Indicates whether this table has selectable columns ($select) + // Columns with an Attachment will be excluded + // Used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/Cloud/DocumentServer.Core/Document/Document/InfoTypes/CdsDataSourceInfo.cs&_a=contents&version=GBmaster + public bool IsSelectable { get; init; } + + public SelectionRestrictions() + { + } + } + + public sealed class FilterRestrictions + { + // List of required properties + public IList RequiredProperties { get; init; } + + // List of non filterable properties (like images) + public IList NonFilterableProperties { get; init; } + + public FilterRestrictions() + { + } + } + + public sealed class SortRestrictions + { + // Columns only supported ASC ordering + public IList AscendingOnlyProperties { get; init; } + + // Columns that don't support ordering + public IList UnsortableProperties { get; init; } + + public SortRestrictions() + { + } + + public SortRestrictions(IList unsortableProperties, IList ascendingOnlyProperties) + { + // List of properties which support ascending order only + AscendingOnlyProperties = ascendingOnlyProperties; + + // List of unsortable properties + UnsortableProperties = unsortableProperties; + } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationCapability.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationCapability.cs index 565a7eb97d..6b55677368 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationCapability.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationCapability.cs @@ -3,16 +3,20 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Numerics; +using System.Text; using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Syntax; namespace Microsoft.PowerFx.Core.Functions.Delegation { // This lightweight wrapper around DelegationCababilityConstants is used to enforce valid values for capabilities. + [DebuggerDisplay("Delegation={DebugString}")] internal struct DelegationCapability { private BigInteger _capabilities; + private static readonly Lazy> _binaryOpToDelegationCapabilityMap = new Lazy>( () => new Dictionary @@ -82,60 +86,62 @@ internal struct DelegationCapability { DelegationMetadataOperatorConstants.CdsIn, new DelegationCapability(CdsIn) }, { DelegationMetadataOperatorConstants.Top, new DelegationCapability(Top) }, { DelegationMetadataOperatorConstants.AsType, new DelegationCapability(AsType) }, - { DelegationMetadataOperatorConstants.ArrayLookup, new DelegationCapability(ArrayLookup) } + { DelegationMetadataOperatorConstants.ArrayLookup, new DelegationCapability(ArrayLookup) }, + { DelegationMetadataOperatorConstants.Distinct, new DelegationCapability(Distinct) } }, isThreadSafe: true); // Supported delegatable operations. - public static readonly BigInteger None = 0x0; - public static readonly BigInteger Sort = 0x1; - public static readonly BigInteger Filter = 0x2; - public static readonly BigInteger GreaterThan = 0x4; - public static readonly BigInteger GreaterThanOrEqual = 0x8; - public static readonly BigInteger LessThan = 0x10; - public static readonly BigInteger LessThanOrEqual = 0x20; - public static readonly BigInteger And = 0x40; - public static readonly BigInteger Or = 0x80; - public static readonly BigInteger In = 0x100; - public static readonly BigInteger Exactin = 0x200; - public static readonly BigInteger Not = 0x400; - public static readonly BigInteger Equal = 0x800; - public static readonly BigInteger NotEqual = 0x1000; - public static readonly BigInteger SortAscendingOnly = 0x2000; - public static readonly BigInteger Contains = 0x4000; - public static readonly BigInteger IndexOf = 0x8000; - public static readonly BigInteger SubStringOf = 0x10000; - public static readonly BigInteger Year = 0x20000; - public static readonly BigInteger Month = 0x40000; - public static readonly BigInteger Day = 0x80000; - public static readonly BigInteger Hour = 0x100000; - public static readonly BigInteger Minute = 0x200000; - public static readonly BigInteger Second = 0x400000; - public static readonly BigInteger Lower = 0x800000; - public static readonly BigInteger Upper = 0x1000000; - public static readonly BigInteger Trim = 0x2000000; - public static readonly BigInteger Null = 0x4000000; - public static readonly BigInteger Date = 0x8000000; - public static readonly BigInteger Length = 0x10000000; - public static readonly BigInteger Sum = 0x20000000; - public static readonly BigInteger Min = 0x40000000; - public static readonly BigInteger Max = 0x80000000; - public static readonly BigInteger Average = 0x100000000; - public static readonly BigInteger Count = 0x200000000; - public static readonly BigInteger Add = 0x400000000; - public static readonly BigInteger Sub = 0x800000000; - public static readonly BigInteger StartsWith = 0x1000000000; - public static readonly BigInteger Mul = 0x2000000000; - public static readonly BigInteger Div = 0x4000000000; - public static readonly BigInteger EndsWith = 0x8000000000; - public static readonly BigInteger CountDistinct = 0x10000000000; - public static readonly BigInteger CdsIn = 0x20000000000; - public static readonly BigInteger Top = 0x40000000000; - public static readonly BigInteger Group = 0x80000000000; - public static readonly BigInteger AsType = 0x100000000000; - public static readonly BigInteger ArrayLookup = 0x200000000000; + public static readonly BigInteger None = BigInteger.Zero; + public static readonly BigInteger Sort = BigInteger.One; // 0x1 + public static readonly BigInteger Filter = BigInteger.Pow(2, 1); // 0x2 + public static readonly BigInteger GreaterThan = BigInteger.Pow(2, 2); // 0x4 + public static readonly BigInteger GreaterThanOrEqual = BigInteger.Pow(2, 3); // 0x8 + public static readonly BigInteger LessThan = BigInteger.Pow(2, 4); // 0x10 + public static readonly BigInteger LessThanOrEqual = BigInteger.Pow(2, 5); // 0x20 + public static readonly BigInteger And = BigInteger.Pow(2, 6); // 0x40 + public static readonly BigInteger Or = BigInteger.Pow(2, 7); // 0x80 + public static readonly BigInteger In = BigInteger.Pow(2, 8); // 0x100 + public static readonly BigInteger Exactin = BigInteger.Pow(2, 9); // 0x200 + public static readonly BigInteger Not = BigInteger.Pow(2, 10); // 0x400 + public static readonly BigInteger Equal = BigInteger.Pow(2, 11); // 0x800 + public static readonly BigInteger NotEqual = BigInteger.Pow(2, 12); // 0x1000 + public static readonly BigInteger SortAscendingOnly = BigInteger.Pow(2, 13); // 0x2000 + public static readonly BigInteger Contains = BigInteger.Pow(2, 14); // 0x4000 + public static readonly BigInteger IndexOf = BigInteger.Pow(2, 15); // 0x8000 + public static readonly BigInteger SubStringOf = BigInteger.Pow(2, 16); // 0x10000 + public static readonly BigInteger Year = BigInteger.Pow(2, 17); // 0x20000 + public static readonly BigInteger Month = BigInteger.Pow(2, 18); // 0x40000 + public static readonly BigInteger Day = BigInteger.Pow(2, 19); // 0x80000 + public static readonly BigInteger Hour = BigInteger.Pow(2, 20); // 0x100000 + public static readonly BigInteger Minute = BigInteger.Pow(2, 21); // 0x200000 + public static readonly BigInteger Second = BigInteger.Pow(2, 22); // 0x400000 + public static readonly BigInteger Lower = BigInteger.Pow(2, 23); // 0x800000 + public static readonly BigInteger Upper = BigInteger.Pow(2, 24); // 0x1000000 + public static readonly BigInteger Trim = BigInteger.Pow(2, 25); // 0x2000000 + public static readonly BigInteger Null = BigInteger.Pow(2, 26); // 0x4000000 + public static readonly BigInteger Date = BigInteger.Pow(2, 27); // 0x8000000 + public static readonly BigInteger Length = BigInteger.Pow(2, 28); // 0x10000000 + public static readonly BigInteger Sum = BigInteger.Pow(2, 29); // 0x20000000 + public static readonly BigInteger Min = BigInteger.Pow(2, 30); // 0x40000000 + public static readonly BigInteger Max = BigInteger.Pow(2, 31); // 0x80000000 + public static readonly BigInteger Average = BigInteger.Pow(2, 32); // 0x100000000 + public static readonly BigInteger Count = BigInteger.Pow(2, 33); // 0x200000000 + public static readonly BigInteger Add = BigInteger.Pow(2, 34); // 0x400000000 + public static readonly BigInteger Sub = BigInteger.Pow(2, 35); // 0x800000000 + public static readonly BigInteger StartsWith = BigInteger.Pow(2, 36); // 0x1000000000 + public static readonly BigInteger Mul = BigInteger.Pow(2, 37); // 0x2000000000 + public static readonly BigInteger Div = BigInteger.Pow(2, 38); // 0x4000000000 + public static readonly BigInteger EndsWith = BigInteger.Pow(2, 39); // 0x8000000000 + public static readonly BigInteger CountDistinct = BigInteger.Pow(2, 40); // 0x10000000000 + public static readonly BigInteger CdsIn = BigInteger.Pow(2, 41); // 0x20000000000 + public static readonly BigInteger Top = BigInteger.Pow(2, 42); // 0x40000000000 + public static readonly BigInteger Group = BigInteger.Pow(2, 43); // 0x80000000000 + public static readonly BigInteger AsType = BigInteger.Pow(2, 44); // 0x100000000000 + public static readonly BigInteger ArrayLookup = BigInteger.Pow(2, 45); // 0x200000000000 + public static readonly BigInteger Distinct = BigInteger.Pow(2, 46); // 0x400000000000 // Please update it as max value changes. - private static BigInteger maxSingleCapabilityValue = ArrayLookup; + private static BigInteger maxSingleCapabilityValue = Distinct; // Indicates support all functionality. public static BigInteger SupportsAll @@ -180,6 +186,311 @@ public bool HasCapability(BigInteger delegationCapability) public BigInteger Capabilities => _capabilities; + internal string DebugString + { + get + { + if (_capabilities.IsZero) + { + return nameof(None); + } + + StringBuilder sb = new StringBuilder(); + + if (HasCapability(Sort)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Sort)); + } + + if (HasCapability(Filter)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Filter)); + } + + if (HasCapability(GreaterThan)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(GreaterThan)); + } + + if (HasCapability(GreaterThanOrEqual)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(GreaterThanOrEqual)); + } + + if (HasCapability(LessThan)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(LessThan)); + } + + if (HasCapability(LessThanOrEqual)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(LessThanOrEqual)); + } + + if (HasCapability(And)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(And)); + } + + if (HasCapability(Or)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Or)); + } + + if (HasCapability(In)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(In)); + } + + if (HasCapability(Exactin)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Exactin)); + } + + if (HasCapability(Not)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Not)); + } + + if (HasCapability(Equal)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Equal)); + } + + if (HasCapability(NotEqual)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(NotEqual)); + } + + if (HasCapability(SortAscendingOnly)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(SortAscendingOnly)); + } + + if (HasCapability(Contains)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Contains)); + } + + if (HasCapability(IndexOf)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(IndexOf)); + } + + if (HasCapability(SubStringOf)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(SubStringOf)); + } + + if (HasCapability(Year)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Year)); + } + + if (HasCapability(Month)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Month)); + } + + if (HasCapability(Day)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Day)); + } + + if (HasCapability(Hour)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Hour)); + } + + if (HasCapability(Minute)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Minute)); + } + + if (HasCapability(Second)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Second)); + } + + if (HasCapability(Lower)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Lower)); + } + + if (HasCapability(Upper)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Upper)); + } + + if (HasCapability(Trim)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Trim)); + } + + if (HasCapability(Null)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Null)); + } + + if (HasCapability(Date)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Date)); + } + + if (HasCapability(Length)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Length)); + } + + if (HasCapability(Sum)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Sum)); + } + + if (HasCapability(Min)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Min)); + } + + if (HasCapability(Max)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Max)); + } + + if (HasCapability(Average)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Average)); + } + + if (HasCapability(Count)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Count)); + } + + if (HasCapability(Add)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Add)); + } + + if (HasCapability(Sub)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Sub)); + } + + if (HasCapability(StartsWith)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(StartsWith)); + } + + if (HasCapability(Mul)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Mul)); + } + + if (HasCapability(Div)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Div)); + } + + if (HasCapability(EndsWith)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(EndsWith)); + } + + if (HasCapability(CountDistinct)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(CountDistinct)); + } + + if (HasCapability(CdsIn)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(CdsIn)); + } + + if (HasCapability(Top)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Top)); + } + + if (HasCapability(Group)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Group)); + } + + if (HasCapability(AsType)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(AsType)); + } + + if (HasCapability(ArrayLookup)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(ArrayLookup)); + } + + if (HasCapability(Distinct)) + { + AddCommaIfNeeded(sb); + sb.Append(nameof(Distinct)); + } + + return sb.ToString(); + } + } + + private static void AddCommaIfNeeded(StringBuilder sb) + { + if (sb.Length != 0) + { + sb.Append(", "); + } + } + public static bool IsValid(BigInteger capabilityConstant) { return (capabilityConstant == None) || diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadata/CdpFilterOpMetadata.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadata/CdpFilterOpMetadata.cs new file mode 100644 index 0000000000..a0268ed6a8 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadata/CdpFilterOpMetadata.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Microsoft.PowerFx.Core.Entities; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Types; + +namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata +{ + internal class CdpFilterOpMetadata : FilterOpMetadata + { + private readonly TableDelegationInfo _delegationInfo; + + public CdpFilterOpMetadata(AggregateType schema, TableDelegationInfo delegationInfo) + : base(schema._type, null, null, GetFilterFunctionsSupportedByAllColumns(delegationInfo), GetFilterFunctionSupportedByTable(delegationInfo)) + { + _delegationInfo = delegationInfo; + } + + private static DelegationCapability GetFilterFunctionsSupportedByAllColumns(TableDelegationInfo delegationInfo) + { + DelegationCapability filterFunctionSupportedByAllColumns = DelegationCapability.None; + + if (delegationInfo?.FilterFunctions != null) + { + foreach (DelegationOperator globalFilterFunctionEnum in delegationInfo.FilterFunctions) + { + string globalFilterFunction = globalFilterFunctionEnum.ToString().ToLowerInvariant(); + + if (DelegationCapability.OperatorToDelegationCapabilityMap.TryGetValue(globalFilterFunction, out DelegationCapability globalFilterFunctionCapability)) + { + filterFunctionSupportedByAllColumns |= globalFilterFunctionCapability | DelegationCapability.Filter; + } + } + } + + return filterFunctionSupportedByAllColumns; + } + + private static DelegationCapability? GetFilterFunctionSupportedByTable(TableDelegationInfo delegationInfo) + { + DelegationCapability? filterFunctionsSupportedByTable = null; + + if (delegationInfo?.FilterSupportedFunctions != null) + { + filterFunctionsSupportedByTable = DelegationCapability.None; + + foreach (DelegationOperator globalSupportedFilterFunctionEnum in delegationInfo.FilterSupportedFunctions) + { + string globalSupportedFilterFunction = globalSupportedFilterFunctionEnum.ToString().ToLowerInvariant(); + + if (DelegationCapability.OperatorToDelegationCapabilityMap.TryGetValue(globalSupportedFilterFunction, out DelegationCapability globalSupportedFilterFunctionCapability)) + { + filterFunctionsSupportedByTable |= globalSupportedFilterFunctionCapability | DelegationCapability.Filter; + } + } + } + + return filterFunctionsSupportedByTable; + } + + public override bool TryGetColumnCapabilities(DPath columnPath, out DelegationCapability capabilities) + { + Contracts.AssertValid(columnPath); + ColumnCapabilitiesDefinition columnCapabilityDefinition = _delegationInfo.GetColumnCapability(columnPath.Name.Value); + + if (columnCapabilityDefinition != null) + { + capabilities = columnCapabilityDefinition.ToDelegationCapability(); + return true; + } + + capabilities = default; + return false; + } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadata/FilterOpMetadata.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadata/FilterOpMetadata.cs index 54c91006a7..1cdf67b775 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadata/FilterOpMetadata.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadata/FilterOpMetadata.cs @@ -8,36 +8,32 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata { // Defines filter operation metadata. - internal sealed class FilterOpMetadata : OperationCapabilityMetadata + internal class FilterOpMetadata : OperationCapabilityMetadata { private readonly Dictionary _columnCapabilities; + private readonly Dictionary _columnRestrictions; + private readonly DelegationCapability? _filterFunctionsSupportedByTable; // Filter functions supported at the table level. // If no capability at column level specified then this would be the default filter functionality supported by column. - private readonly DelegationCapability _defaultCapabilities; - - public FilterOpMetadata( - DType tableSchema, - Dictionary columnRestrictions, - Dictionary columnCapabilities, - DelegationCapability filterFunctionsSupportedByAllColumns, - DelegationCapability? filterFunctionsSupportedByTable) - : base(tableSchema) - { - Contracts.AssertValue(columnRestrictions); - Contracts.AssertValue(columnCapabilities); + private readonly DelegationCapability _defaultCapabilities; + public FilterOpMetadata(DType tableSchema, Dictionary columnRestrictions, Dictionary columnCapabilities, DelegationCapability filterFunctionsSupportedByAllColumns, DelegationCapability? filterFunctionsSupportedByTable) + : base(tableSchema) + { _columnCapabilities = columnCapabilities; _columnRestrictions = columnRestrictions; + _filterFunctionsSupportedByTable = filterFunctionsSupportedByTable; _defaultCapabilities = filterFunctionsSupportedByAllColumns; + if (_filterFunctionsSupportedByTable != null) { _defaultCapabilities = filterFunctionsSupportedByAllColumns | DelegationCapability.Filter; } - } + } protected override Dictionary ColumnRestrictions => _columnRestrictions; @@ -55,7 +51,7 @@ public override DelegationCapability TableCapabilities { // If there are no capabilities defined at column level then filter is not supported. // Otherwise this simply means that filter operators at table level are not supported. - // For example, Filter(CDS, Lower(Col1) != Lower(Col2)), here != operator at table level needs to be supported as it's not operating on any column directly. + // For example, Filter(CDS, Lower(Col1) != Lower(Col2)), here != operator at table level needs to be supported as it's not operating on any column directly. if (DefaultColumnCapabilities.Capabilities == DelegationCapability.None) { return DelegationCapability.None; @@ -71,7 +67,7 @@ public override DelegationCapability TableCapabilities public override bool TryGetColumnCapabilities(DPath columnPath, out DelegationCapability capabilities) { Contracts.AssertValid(columnPath); - + // See if there is a specific capability defined for column. // If not then just return default one. if (!_columnCapabilities.TryGetValue(columnPath, out capabilities)) diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadataOperatorConstants.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadataOperatorConstants.cs index 3a33a13e88..7c168d6148 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadataOperatorConstants.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/Delegation/DelegationMetadataOperatorConstants.cs @@ -51,5 +51,51 @@ internal static class DelegationMetadataOperatorConstants public const string Top = "top"; public const string AsType = "astype"; public const string ArrayLookup = "arraylookup"; + public const string Distinct = "distinct"; + } + + public enum DelegationOperator + { + Eq, + Ne, + Lt, + Le, + Gt, + Ge, + And, + Or, + Contains, + Indexof, + Substringof, + Not, + Year, + Month, + Day, + Hour, + Minute, + Second, + Tolower, + Toupper, + Trim, + Null, + Date, + Length, + Sum, + Min, + Max, + Average, + Count, + Add, + Sub, + Startswith, + Mul, + Div, + Endswith, + Countdistinct, + Cdsin, + Top, + Astype, + Arraylookup, + Distinct } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs b/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs index 812122724f..f86086b4e6 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Functions/UserDefinedFunction.cs @@ -75,6 +75,8 @@ public override bool TryGetDataSource(CallNode callNode, TexlBinding binding, ou return TryGetExternalDataSource(out dsInfo); } + public bool HasDelegationWarning => _binding?.ErrorContainer.GetErrors().Any(error => error.MessageKey.Contains("SuggestRemoteExecutionHint")) ?? false; + /// /// Initializes a new instance of the class. /// @@ -147,7 +149,7 @@ public TexlBinding BindBody(INameResolver nameResolver, IBinderGlue documentBind throw new InvalidOperationException($"Body should only get bound once: {this.Name}"); } - bindingConfig = bindingConfig ?? new BindingConfig(this._isImperative); + bindingConfig = bindingConfig ?? new BindingConfig(this._isImperative, userDefinitionsMode: true); _binding = TexlBinding.Run(documentBinderGlue, UdfBody, UserDefinitionsNameResolver.Create(nameResolver, _args, ParamTypes), bindingConfig, features: features, rule: rule); CheckTypesOnDeclaration(_binding.CheckTypesContext, _binding.ResultType, _binding); @@ -172,7 +174,8 @@ public void CheckTypesOnDeclaration(CheckTypesContext context, DType actualBodyR } else { - binding.ErrorContainer.EnsureError(DocumentErrorSeverity.Severe, UdfBody, TexlStrings.ErrUDF_ReturnTypeDoesNotMatch, ReturnType.GetKindString(), actualBodyReturnType.GetKindString()); + var node = UdfBody is VariadicOpNode variadicOpNode ? variadicOpNode.Children.Last() : UdfBody; + binding.ErrorContainer.EnsureError(DocumentErrorSeverity.Severe, node, TexlStrings.ErrUDF_ReturnTypeDoesNotMatch, ReturnType.GetKindString(), actualBodyReturnType.GetKindString()); } } } 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/ExpressionLocalizationHelper.cs b/src/libraries/Microsoft.PowerFx.Core/Localization/ExpressionLocalizationHelper.cs index 2080a47d24..d3ce64e3e6 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Localization/ExpressionLocalizationHelper.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Localization/ExpressionLocalizationHelper.cs @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -using System; -using System.Collections.Generic; -using System.Globalization; -using Microsoft.PowerFx.Core.Binding; -using Microsoft.PowerFx.Core.Glue; -using Microsoft.PowerFx.Syntax; -using Microsoft.PowerFx.Types; - -namespace Microsoft.PowerFx.Core -{ - internal class ExpressionLocalizationHelper - { - internal static string ConvertExpression(string expressionText, RecordType parameters, BindingConfig bindingConfig, INameResolver resolver, IBinderGlue binderGlue, ParserOptions options, Features flags, bool toDisplay) - { - var targetLexer = toDisplay ? TexlLexer.GetLocalizedInstance(options?.Culture ?? CultureInfo.InvariantCulture) : TexlLexer.InvariantLexer; - var sourceLexer = toDisplay ? TexlLexer.InvariantLexer : TexlLexer.GetLocalizedInstance(options?.Culture ?? CultureInfo.InvariantCulture); - - var worklist = GetLocaleSpecificTokenConversions(expressionText, sourceLexer, targetLexer); - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.PowerFx.Core.Binding; +using Microsoft.PowerFx.Core.Glue; +using Microsoft.PowerFx.Syntax; +using Microsoft.PowerFx.Types; + +namespace Microsoft.PowerFx.Core +{ + internal class ExpressionLocalizationHelper + { + internal static string ConvertExpression(string expressionText, RecordType parameters, BindingConfig bindingConfig, INameResolver resolver, IBinderGlue binderGlue, ParserOptions options, Features flags, bool toDisplay) + { + var targetLexer = toDisplay ? TexlLexer.GetLocalizedInstance(options?.Culture ?? CultureInfo.InvariantCulture) : TexlLexer.InvariantLexer; + var sourceLexer = toDisplay ? TexlLexer.InvariantLexer : TexlLexer.GetLocalizedInstance(options?.Culture ?? CultureInfo.InvariantCulture); + + var worklist = GetLocaleSpecificTokenConversions(expressionText, sourceLexer, targetLexer); + var formula = new Formula(expressionText, toDisplay ? CultureInfo.InvariantCulture : options?.Culture ?? CultureInfo.InvariantCulture); - formula.EnsureParsed(options.GetParserFlags()); - - var binding = TexlBinding.Run( - binderGlue, - null, - new Core.Entities.QueryOptions.DataSourceToQueryOptionsMap(), - formula.ParseTree, - resolver, - bindingConfig, - ruleScope: parameters?._type, - updateDisplayNames: toDisplay, - forceUpdateDisplayNames: toDisplay, - features: flags); - - foreach (var token in binding.NodesToReplace) + formula.EnsureParsed(options.GetParserFlags()); + + var binding = TexlBinding.Run( + binderGlue, + null, + new Core.Entities.QueryOptions.DataSourceToQueryOptionsMap(), + formula.ParseTree, + resolver, + bindingConfig, + ruleScope: parameters?._type, + updateDisplayNames: toDisplay, + forceUpdateDisplayNames: toDisplay, + features: flags); + + foreach (var token in binding.NodesToReplace) { worklist.Add(token.Key.Span, TexlLexer.EscapeName(token.Value)); } @@ -49,7 +49,7 @@ internal static string ConvertExpression(string expressionText, RecordType param return ConvertExpression(expressionText, parameters, bindingConfig, resolver, binderGlue, new ParserOptions() { Culture = culture }, flags, toDisplay); } - private static IDictionary GetLocaleSpecificTokenConversions(string script, TexlLexer sourceLexer, TexlLexer targetLexer) + internal static IDictionary GetLocaleSpecificTokenConversions(string script, TexlLexer sourceLexer, TexlLexer targetLexer) { var worklist = new Dictionary(); @@ -104,4 +104,4 @@ private static IDictionary GetLocaleSpecificTokenConversions(strin return worklist; } } -} +} diff --git a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs index f1dd1963b3..4b399d6444 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Localization/Strings.cs @@ -677,6 +677,7 @@ internal static class TexlStrings public static ErrorResourceKey ErrColonExpected = new ErrorResourceKey("ErrColonExpected"); public static ErrorResourceKey ErrExpectedDataSourceRestriction = new ErrorResourceKey("ErrExpectedDataSourceRestriction"); public static ErrorResourceKey ErrBehaviorPropertyExpected = new ErrorResourceKey("ErrBehaviorPropertyExpected"); + public static ErrorResourceKey ErrBehaviorFunctionInDataUDF = new ErrorResourceKey("ErrBehaviorFunctionInDataUDF"); public static ErrorResourceKey ErrTestPropertyExpected = new ErrorResourceKey("ErrTestPropertyExpected"); public static ErrorResourceKey ErrStringExpected = new ErrorResourceKey("ErrStringExpected"); public static ErrorResourceKey ErrDateExpected = new ErrorResourceKey("ErrDateExpected"); @@ -757,12 +758,15 @@ internal static class TexlStrings public static ErrorResourceKey SuggestRemoteExecutionHint_InOpRhs = new ErrorResourceKey("SuggestRemoteExecutionHint_InOpRhs"); public static ErrorResourceKey SuggestRemoteExecutionHint_StringMatchSecondParam = new ErrorResourceKey("SuggestRemoteExecutionHint_StringMatchSecondParam"); public static ErrorResourceKey SuggestRemoteExecutionHint_InOpInvalidColumn = new ErrorResourceKey("SuggestRemoteExecutionHint_InOpInvalidColumn"); + public static ErrorResourceKey SuggestRemoteExecutionHint_NF = new ErrorResourceKey("SuggestRemoteExecutionHint_NF"); + public static ErrorResourceKey SuggestRemoteExecutionHint_UDF = new ErrorResourceKey("SuggestRemoteExecutionHint_UDF"); public static ErrorResourceKey OpNotSupportedByColumnSuggestionMessage_OpNotSupportedByColumn = new ErrorResourceKey("SuggestRemoteExecutionHint_OpNotSupportedByColumn"); public static ErrorResourceKey OpNotSupportedByServiceSuggestionMessage_OpNotSupportedByService = new ErrorResourceKey("SuggestRemoteExecutionHint_OpNotSupportedByService"); public static ErrorResourceKey OpNotSupportedByClientSuggestionMessage_OpNotSupportedByClient = new ErrorResourceKey("SuggestRemoteExecutionHint_OpNotSupportedByClient"); 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"); @@ -847,5 +851,7 @@ internal static class TexlStrings public static ErrorResourceKey ErrUnsupportedTypeInTypeArgument = new ErrorResourceKey("ErrUnsupportedTypeInTypeArgument"); public static ErrorResourceKey ErrReachedMaxJsonDepth = new ErrorResourceKey("ErrReachedMaxJsonDepth"); public static ErrorResourceKey ErrReachedMaxJsonLength = new ErrorResourceKey("ErrReachedMaxJsonLength"); + + public static ErrorResourceKey ErrUserDefinedTypesDisabled = new ErrorResourceKey("ErrUserDefinedTypesDisabled"); } } 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/Config/ComposedReadOnlySymbolTable.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/ComposedReadOnlySymbolTable.cs index dabcae8191..70da8c4765 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/ComposedReadOnlySymbolTable.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/ComposedReadOnlySymbolTable.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection.Metadata; using Microsoft.PowerFx.Core.Binding; using Microsoft.PowerFx.Core.Binding.BindInfo; using Microsoft.PowerFx.Core.Entities; @@ -163,6 +164,20 @@ IEnumerable IEnumStore.EnumSymbols } } + public override IEnumerable> OptionSets + { + get + { + foreach (ReadOnlySymbolTable st in _symbolTables) + { + foreach (KeyValuePair kvp in st.OptionSets) + { + yield return kvp; + } + } + } + } + public virtual bool Lookup(DName name, out NameLookupInfo nameInfo, NameLookupPreferences preferences = NameLookupPreferences.None) { foreach (INameResolver table in _symbolTables) diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs index 2e4c83cd35..4c4ec848fb 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/Features.cs @@ -91,6 +91,11 @@ public sealed class Features /// internal bool IsLookUpReductionDelegationEnabled { get; set; } + /// + /// Enables User-defined types functionality. + /// + internal bool IsUserDefinedTypesEnabled { get; set; } = false; + internal static Features None => new Features(); public static Features PowerFxV1 => new Features @@ -105,7 +110,8 @@ public sealed class Features PowerFxV1CompatibilityRules = true, PrimaryOutputPropertyCoercionDeprecated = true, AsTypeLegacyCheck = false, - JsonFunctionAcceptsLazyTypes = true + JsonFunctionAcceptsLazyTypes = true, + IsUserDefinedTypesEnabled = true }; internal Features() @@ -123,6 +129,7 @@ internal Features(Features other) FirstLastNRequiresSecondArguments = other.FirstLastNRequiresSecondArguments; PowerFxV1CompatibilityRules = other.PowerFxV1CompatibilityRules; PrimaryOutputPropertyCoercionDeprecated = other.PrimaryOutputPropertyCoercionDeprecated; + IsUserDefinedTypesEnabled = other.IsUserDefinedTypesEnabled; } } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/PowerFxConfig.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/PowerFxConfig.cs index edf9a73b48..eacf0b3f4d 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/PowerFxConfig.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/PowerFxConfig.cs @@ -163,7 +163,7 @@ internal void AddFunctions(TexlFunctionSet functionSet) public void AddOptionSet(OptionSet optionSet, DName optionalDisplayName = default) { - AddEntity(optionSet, optionalDisplayName); + SymbolTable.AddOptionSet(optionSet, optionalDisplayName); } internal bool TryGetVariable(DName name, out DName displayName) diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/ReadOnlySymbolTable.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/ReadOnlySymbolTable.cs index eee2458ad3..b9bba138e4 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/ReadOnlySymbolTable.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/ReadOnlySymbolTable.cs @@ -347,6 +347,9 @@ public SymbolTable GetMutableCopyOfFunctions() public IEnumerable FunctionNames => this.Functions.FunctionNames; + public virtual IEnumerable> OptionSets => _variables.Where(kvp => kvp.Value.Kind == BindKind.OptionSet) + .Select(kvp => new KeyValuePair(kvp.Key, (OptionSet)kvp.Value.Data)); + // Which enums are available. // These do not compose - only bottom one wins. // ComposedReadOnlySymbolTable will handle composition by looking up in each symbol table. diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs index a8aa0d3ec9..b635b9f572 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Config/SymbolTable.cs @@ -339,6 +339,11 @@ internal EnumStoreBuilder EnumStoreBuilder _enumStoreBuilder = value; } } + + public void AddOptionSet(OptionSet optionSet, DName displayName = default) + { + AddEntity(optionSet, displayName); + } internal void AddEntity(IExternalEntity entity, DName displayName = default) { diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs b/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs index 4dad15d5c5..7b708f3c31 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/DefinitionsCheckResult.cs @@ -168,12 +168,12 @@ internal TexlFunctionSet ApplyCreateUserDefinedFunctions() var composedSymbols = ReadOnlySymbolTable.Compose(_localSymbolTable, _symbols); foreach (var udf in partialUDFs) { - var config = new BindingConfig(allowsSideEffects: _parserOptions.AllowsSideEffects, useThisRecordForRuleScope: false, numberIsFloat: false); + var config = new BindingConfig(allowsSideEffects: _parserOptions.AllowsSideEffects, useThisRecordForRuleScope: false, numberIsFloat: false, userDefinitionsMode: true); var binding = udf.BindBody(composedSymbols, new Glue2DocumentBinderGlue(), config); List bindErrors = new List(); - if (binding.ErrorContainer.HasErrors()) + if (binding.ErrorContainer.GetErrors().Any(error => error.Severity > DocumentErrorSeverity.Warning)) { _errors.AddRange(ExpressionError.New(binding.ErrorContainer.GetErrors(), _defaultErrorCulture)); } diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/ExpressionError.cs b/src/libraries/Microsoft.PowerFx.Core/Public/ExpressionError.cs index 50b43d131b..92f8e77565 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/ExpressionError.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/ExpressionError.cs @@ -39,10 +39,15 @@ public string Message // If this is set directly, it will skip localization. set => _message = value; - } + } + + /// + /// Optional - provide file context for where this expression is from. + /// + public FileLocation FragmentLocation { get; set; } /// - /// Source location for this error. + /// Source location for this error within a single expression. /// public Span Span { get; set; } @@ -209,6 +214,24 @@ internal static IEnumerable New(IEnumerable err return errors.Select(x => ExpressionError.New(x, locale)).ToArray(); } } + + // Translate Span in original text (start,end) to something more useful for a file. + internal static IEnumerable NewFragment(IEnumerable errors, string originalText, FileLocation fragmentLocation) + { + if (errors == null) + { + return Array.Empty(); + } + else + { + return errors.Select(x => + { + var error = ExpressionError.New(x, null); + error.FragmentLocation = fragmentLocation.Apply(originalText, error.Span); + return error; + }); + } + } } /// diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/FileLocation.cs b/src/libraries/Microsoft.PowerFx.Core/Public/FileLocation.cs new file mode 100644 index 0000000000..7032b7af12 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Core/Public/FileLocation.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.PowerFx.Core.Errors; +using Microsoft.PowerFx.Core.Localization; +using Microsoft.PowerFx.Types; + +namespace Microsoft.PowerFx.Syntax +{ + /// + /// File-aware, Multi-line source span with Line and Column. + /// Wheras is a character span within a single expression. + /// + public class FileLocation + { + public string Filename { get; init; } + + // 1-based index + public int LineStart { get; init; } + + // 1-based index. + public int ColStart { get; init; } + + /// + /// Apply a span (which is a character offset within a single expression) + /// to this file offset. + /// + /// + /// + /// + public FileLocation Apply(string originalText, Span s2) + { + // Convert index span to line span. + int iLine = this.LineStart; + int iCol = ColStart; + + for (int i = 0; i < s2.Min; i++) + { + if (originalText[i] == '\r') + { + // ignore + } + else if (originalText[i] == '\n') + { + iLine++; + iCol = ColStart; // reset + } + else + { + iCol++; + } + } + + return new FileLocation + { + Filename = Filename, + ColStart = iCol, + LineStart = iLine + }; + } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Types/AggregateType.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Types/AggregateType.cs index e4a96c7e45..ac896367ff 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Types/AggregateType.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Types/AggregateType.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Xml.Linq; using Microsoft.PowerFx.Core; using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Core.Utils; @@ -58,6 +57,13 @@ public virtual bool TryGetFieldType(string name, out FormulaType type) return true; } + // When a relationship is used in connected types, this returns the 'base' type of the field + // without retrieving the type issued from the relationship + public virtual bool TryGetUnderlyingFieldType(string name, out FormulaType type) + { + return TryGetFieldType(name, out type); + } + internal bool TryGetBackingDType(string fieldName, out DType type) { if (_type == null) diff --git a/src/libraries/Microsoft.PowerFx.Core/Public/Types/RecordType.cs b/src/libraries/Microsoft.PowerFx.Core/Public/Types/RecordType.cs index 193cccc073..86304e59e4 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Public/Types/RecordType.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Public/Types/RecordType.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Core.Utils; using Microsoft.PowerFx.Syntax; @@ -43,12 +43,29 @@ public RecordType() /// Derived classes calling this must override /// and . /// - /// Provide DispayNamerovide to be used. + /// Provide DisplayNameProvider to be used. public RecordType(DisplayNameProvider displayNameProvider) : base(false, displayNameProvider) + { + } + + /// + /// Initializes a new instance of the class with and . + /// Derived classes calling this must override . + /// + /// Provide DisplayNameProvider to be used. + /// Table provider to be used. + public RecordType(DisplayNameProvider displayNameProvider, TableDelegationInfo delegationInfo) + : base(false, displayNameProvider) { + _type = DType.AttachDataSourceInfo(_type, new DataSourceInfo(this, displayNameProvider, delegationInfo)); + _fieldNames = displayNameProvider.LogicalToDisplayPairs.Select(pair => pair.Key.Value).ToList(); } + public override IEnumerable FieldNames => _fieldNames; + + private readonly IEnumerable _fieldNames = null; + public override void Visit(ITypeVisitor vistor) { vistor.Visit(this); diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs index a46e801686..240fd0d809 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/BuiltinFunctionsCore.cs @@ -127,7 +127,8 @@ internal class BuiltinFunctionsCore public static readonly TexlFunction IsBlankOptionSetValue = _library.Add(new IsBlankOptionSetValueFunction()); public static readonly TexlFunction IsBlankOrError = _library.Add(new IsBlankOrErrorFunction()); public static readonly TexlFunction IsBlankOrErrorOptionSetValue = _library.Add(new IsBlankOrErrorOptionSetValueFunction()); - public static readonly TexlFunction IsEmpty = _library.Add(new IsEmptyFunction()); + public static readonly TexlFunction IsEmpty = _library.Add(new IsEmptyFunction()); + public static readonly TexlFunction IsEmpty_UO = _library.Add(new IsEmptyFunction_UO()); public static readonly TexlFunction IsError = _library.Add(new IsErrorFunction()); public static readonly TexlFunction IsNumeric = _library.Add(new IsNumericFunction()); public static readonly TexlFunction ISOWeekNum = _library.Add(new ISOWeekNumFunction()); @@ -253,7 +254,7 @@ internal class BuiltinFunctionsCore public static readonly TexlFunction Decimal = _featureGateFunctions.Add(new DecimalFunction()); public static readonly TexlFunction Decimal_UO = _featureGateFunctions.Add(new DecimalFunction_UO()); public static readonly TexlFunction Float = _featureGateFunctions.Add(new FloatFunction()); - public static readonly TexlFunction Float_UO = _featureGateFunctions.Add(new FloatFunction_UO()); + public static readonly TexlFunction Float_UO = _featureGateFunctions.Add(new FloatFunction_UO()); public static readonly TexlFunction IsUTCToday = _featureGateFunctions.Add(new IsUTCTodayFunction()); public static readonly TexlFunction UTCNow = _featureGateFunctions.Add(new UTCNowFunction()); public static readonly TexlFunction UTCToday = _featureGateFunctions.Add(new UTCTodayFunction()); diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsEmpty.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsEmpty.cs index 6633acc9c5..3b6c9194ac 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsEmpty.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/IsEmpty.cs @@ -68,4 +68,19 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp return fValid; } } + + internal sealed class IsEmptyFunction_UO : BuiltinFunction + { + public override bool IsSelfContained => true; + + public IsEmptyFunction_UO() + : base("IsEmpty", TexlStrings.AboutIsEmpty, FunctionCategories.Table | FunctionCategories.Information, DType.Boolean, 0, 1, 1, DType.UntypedObject) + { + } + + public override IEnumerable GetSignatures() + { + yield return new[] { TexlStrings.IsEmptyArg1 }; + } + } } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs index b80f141819..11951e854f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/Json.cs @@ -82,13 +82,6 @@ public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DTyp { return false; } - - TexlNode optionsNode = args[1]; - if (!IsConstant(context, argTypes, optionsNode, out string nodeValue)) - { - errors.EnsureError(optionsNode, TexlStrings.ErrFunctionArg2ParamMustBeConstant, "JSON", TexlStrings.JSONArg2.Invoke()); - return false; - } } return true; @@ -123,6 +116,7 @@ public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[ TexlNode optionsNode = args[1]; if (!IsConstant(binding.CheckTypesContext, argTypes, optionsNode, out string nodeValue)) { + errors.EnsureError(optionsNode, TexlStrings.ErrFunctionArg2ParamMustBeConstant, "JSON", TexlStrings.JSONArg2.Invoke()); return; } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/RenameColumns.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/RenameColumns.cs index b76e6a5dc2..7044d0d9a5 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/RenameColumns.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/RenameColumns.cs @@ -239,7 +239,7 @@ public override bool UpdateDataQuerySelects(CallNode callNode, TexlBinding bindi for (var i = 1; i < args.Count - 1; i += 2) { base.TryGetColumnLogicalName(dsType, binding.Features.SupportColumnNamesAsIdentifiers, args[i], DefaultErrorContainer, out var columnName, out var columnType).Verify(); - Contracts.Assert(dsType.Contains(columnName)); + Contracts.Assert(dsType.Kind == DKind.LazyTable || dsType.Contains(columnName)); retval |= dsType.AssociateDataSourcesToSelect(dataSourceToQueryOptionsMap, columnName, columnType, true); } diff --git a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ShowColumns.cs b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ShowColumns.cs index db58417235..3657ce496f 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ShowColumns.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Texl/Builtins/ShowColumns.cs @@ -46,7 +46,7 @@ public override bool UpdateDataQuerySelects(CallNode callNode, TexlBinding bindi var columnType = typedName.Type; var columnName = typedName.Name.Value; - Contracts.Assert(dsType.Contains(new DName(columnName))); + Contracts.Assert(dsType.Kind == DKind.LazyTable || dsType.Contains(new DName(columnName))); retval |= dsType.AssociateDataSourcesToSelect(dataSourceToQueryOptionsMap, columnName, columnType, true); } diff --git a/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs b/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs index ef2fafe646..b70fec26c5 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Types/DType.cs @@ -515,7 +515,7 @@ private DType(DKind kind, string namedValueKind) AssertValid(); } - internal DType(LazyTypeProvider provider, bool isTable, DisplayNameProvider displayNameProvider = null) + internal DType(LazyTypeProvider provider, bool isTable, DisplayNameProvider displayNameProvider = null, HashSet associatedDataSources = null) { Contracts.AssertValue(provider); @@ -528,7 +528,7 @@ internal DType(LazyTypeProvider provider, bool isTable, DisplayNameProvider disp ExpandInfo = null; PolymorphicInfo = null; Metadata = null; - AssociatedDataSources = new HashSet(); + AssociatedDataSources = associatedDataSources ?? new HashSet(); OptionSetInfo = null; ViewInfo = null; NamedValueKind = null; @@ -1069,7 +1069,7 @@ public DType ToRecord(ref bool fError) case DKind.Record: return this; case DKind.LazyTable: - return new DType(LazyTypeProvider, isTable: false, DisplayNameProvider); + return new DType(LazyTypeProvider, isTable: false, DisplayNameProvider, AssociatedDataSources); case DKind.Table: case DKind.DataEntity: case DKind.Control: @@ -1113,7 +1113,7 @@ public DType ToTable(ref bool fError) case DKind.Table: return this; case DKind.LazyRecord: - return new DType(LazyTypeProvider, isTable: true, DisplayNameProvider); + return new DType(LazyTypeProvider, isTable: true, DisplayNameProvider, AssociatedDataSources); case DKind.Record: case DKind.DataEntity: case DKind.Control: diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs index ebdaa102c5..69db48a8a1 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/Environment/PowerFxConfigExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.PowerFx.Core.Texl.Builtins; using Microsoft.PowerFx.Functions; using Microsoft.PowerFx.Interpreter; +using Microsoft.PowerFx.Types; namespace Microsoft.PowerFx { @@ -22,6 +23,12 @@ public static void AddFunction(this SymbolTable symbolTable, ReflectionFunction symbolTable.AddFunction(function.GetTexlFunction()); } + public static void AddEnvironmentVariables(this SymbolValues symbolValues, RecordValue recordValue) + { + var variablesRecordValue = FormulaValue.NewRecordFromFields(new NamedValue("Variables", recordValue)); + symbolValues.Add("Environment", variablesRecordValue); + } + /// /// Enable a Set() function which allows scripts to do . /// diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs index 367e632f10..ab580f2dd0 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/Library.cs @@ -982,6 +982,17 @@ static Library() returnBehavior: ReturnBehavior.AlwaysEvaluateAndReturnResult, targetFunction: IsEmpty) }, + { + BuiltinFunctionsCore.IsEmpty_UO, + StandardErrorHandling( + BuiltinFunctionsCore.IsEmpty_UO.Name, + expandArguments: NoArgExpansion, + replaceBlankValues: DoNotReplaceBlank, + checkRuntimeTypes: ExactValueTypeOrBlank, + checkRuntimeValues: UntypedObjectArrayChecker, + returnBehavior: ReturnBehavior.ReturnBlankIfAnyArgIsBlank, + targetFunction: IsEmpty_UO) + }, { BuiltinFunctionsCore.IsError, NoErrorHandling(IsError) diff --git a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryText.cs b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryText.cs index 2b79019ab7..025d09345c 100644 --- a/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryText.cs +++ b/src/libraries/Microsoft.PowerFx.Interpreter/Functions/LibraryText.cs @@ -47,8 +47,8 @@ internal static partial class Library private static readonly Regex _secondsDetokenizeRegex = new Regex("[\u0008][\u0008]+", RegExFlags); private static readonly Regex _milisecondsDetokenizeRegex = new Regex("[\u000e]+", RegExFlags); private static readonly Regex _tdTagRegex = new Regex("<\\s*(td)[\\s\\S]*?\\/{0,1}>", RegExFlags_IgnoreCase); - private static readonly Regex _lineBreakTagRegex = new Regex("<\\s*(br|li)[\\s\\S]*?\\/{0,1}>", RegExFlags_IgnoreCase); - private static readonly Regex _doubleLineBreakTagRegex = new Regex("<\\s*(div|p|tr)[\\s\\S]*?\\/{0,1}>", RegExFlags_IgnoreCase); + private static readonly Regex _lineBreakTagRegex = new Regex("<\\s*(br|li)((\\s+[\\s\\S]*?)|(\\s*\\/\\s*))?>", RegExFlags_IgnoreCase); + private static readonly Regex _doubleLineBreakTagRegex = new Regex("<\\s*(div|p|tr)((\\s+[\\s\\S]*?)|(\\s*\\/\\s*))?>", RegExFlags_IgnoreCase); private static readonly Regex _commentTagRegex = new Regex(" TexlFunctions + // fail if duplicates detected within this batch. + // fail if any names are resverd + // Basic type checking + IEnumerable udfs = UserDefinedFunction.CreateFunctions(parseResult.UDFs, s2, out var errors3); + + errors.AddRange(ExpressionError.NewFragment(errors3, str, fragmentLocation)); + if (errors.Any(x => !x.IsWarning)) + { + return false; + } + + // Body can refer to other functions defined in this batch. So we need 2 pass. + // First add all definitions. + foreach (var udf in udfs) + { + moduleExports.AddFunction(udf); + } + + // Then bind all bodies. + foreach (var udf in udfs) + { + var config = new BindingConfig(allowsSideEffects: allowSideEffects, useThisRecordForRuleScope: false, numberIsFloat: false); + + Features features = Features.PowerFxV1; + + var binding = udf.BindBody(s2, new Glue2DocumentBinderGlue(), config, features); + + List bindErrors = new List(); + + binding.ErrorContainer.GetErrors(ref bindErrors); + errors.AddRange(ExpressionError.NewFragment(bindErrors, str, fragmentLocation)); + + if (errors.Any(x => !x.IsWarning)) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Repl/Modules/ModulePoco.cs b/src/libraries/Microsoft.PowerFx.Repl/Modules/ModulePoco.cs new file mode 100644 index 0000000000..1943727b84 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Repl/Modules/ModulePoco.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Microsoft.PowerFx.Syntax; + +namespace Microsoft.PowerFx.Repl +{ + /// + /// The yaml representation of a module. + /// Properties with 'Src_' prefix are set by deserializer and not part of the yaml file contents. + /// + internal class ModulePoco + { + /// + /// Full path of file this was loaded from. + /// This is set by the deserializer. + /// + public string Src_Filename { get; set; } + + public ModuleIdentity GetIdentity() + { + return ModuleIdentity.FromFile(Src_Filename); + } + + /// + /// Contain Power Fx UDF declarations. + /// + public StringWithSource Formulas { get; set; } + + /// + /// Set of modules that this depends on. + /// + public ImportPoco[] Imports { get; set; } + } + + internal class ImportPoco + { + /// + /// File path to import from. "foo.fx.yml" or ".\path\foo.fx.yml". + /// + public string File { get; set; } + + // identifier resolved against the host. + public string Host { get; set; } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Repl/Modules/StringWithSource.cs b/src/libraries/Microsoft.PowerFx.Repl/Modules/StringWithSource.cs new file mode 100644 index 0000000000..69e72ffc12 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Repl/Modules/StringWithSource.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Microsoft.PowerFx.Syntax; + +namespace Microsoft.PowerFx.Repl +{ + /// + /// This is a string object, but also tagged with a solution location for where the string contents start. + /// This is restircted to Inline strings (and rejects other formats like fodling, quotes, etc). + /// Use a custom converter to populate the source location of the string. See . + /// + internal class StringWithSource + { + /// + /// The contents of the string from the yaml. + /// + public string Value { get; set; } + + /// + /// The location in the file for where the contents start. Useful for error reporting. + /// + public FileLocation Location { get; set; } + } +} diff --git a/src/libraries/Microsoft.PowerFx.Repl/Modules/StringWithSourceConverter.cs b/src/libraries/Microsoft.PowerFx.Repl/Modules/StringWithSourceConverter.cs new file mode 100644 index 0000000000..ef59632107 --- /dev/null +++ b/src/libraries/Microsoft.PowerFx.Repl/Modules/StringWithSourceConverter.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.PowerFx.Core.Binding; +using Microsoft.PowerFx.Core.Errors; +using Microsoft.PowerFx.Core.Functions; +using Microsoft.PowerFx.Core.Glue; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Syntax; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NodeDeserializers; + +namespace Microsoft.PowerFx.Repl +{ + /// + /// Yaml converter to parse a and tag + /// it with the source location. + /// + internal class StringWithSourceConverter : IYamlTypeConverter + { + private readonly string _filename; + private readonly string[] _lines; // to infer indent level. + + /// + /// Initializes a new instance of the class. + /// + /// filename to tag with. + /// contents of the file - this is used to infer indenting depth that can't be gotten from th yaml parser. + public StringWithSourceConverter(string filename, string contents) + { + _filename = filename; + + _lines = contents.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + } + + public bool Accepts(Type type) + { + return type == typeof(StringWithSource); + } + + public object ReadYaml(IParser parser, Type type) + { + var val = parser.Consume(); + + // Empty case + if (val.Style == ScalarStyle.Plain && + val.Value.Length == 0) + { + return new StringWithSource + { + Value = null, // Preserve same semantics as String deserialization. + Location = new FileLocation + { + Filename = _filename, + LineStart = val.Start.Line, + ColStart = val.Start.Column + } + }; + } + + // https://stackoverflow.com/questions/3790454/how-do-i-break-a-string-in-yaml-over-multiple-lines + // Yaml has many multi-line encodings. Fortunately, we can determine this based on ScalarStyle + // We want to force | , which is Literal. This means the fx is copy & paste into yaml, with an indent level + // and no other mutation. + // + // This will prevent all other forms, including: + // > is "fold" which will remove newlines. + // "" is double quotes. + // plain where the string is single-line embedded. + + if (val.Style != ScalarStyle.Literal) + { + // This is important if we want to preserve character index mapping. + throw new InvalidOperationException($"must be literal string encoding at {val.Start.Line}"); + } + + int line1PropertyName = val.Start.Line; // 1-based Line that the "Formulas" property is on. Content starts on next line. + + // YamlParser does not tell us indent level. Must discern it. + var line = _lines[line1PropertyName]; + var colStart0 = line.FindIndex(x => !char.IsWhiteSpace(x)); + + return new StringWithSource + { + Value = val.Value, + Location = new FileLocation + { + Filename = _filename, + LineStart = line1PropertyName + 1, + ColStart = colStart0 + 1 + } + }; + } + + public void WriteYaml(IEmitter emitter, object value, Type type) + { + // Only reads. + throw new NotImplementedException(); + } + } // end class +} diff --git a/src/libraries/Microsoft.PowerFx.Repl/Repl.cs b/src/libraries/Microsoft.PowerFx.Repl/Repl.cs index 1d0657ff1b..ef4d41d9e7 100644 --- a/src/libraries/Microsoft.PowerFx.Repl/Repl.cs +++ b/src/libraries/Microsoft.PowerFx.Repl/Repl.cs @@ -37,6 +37,13 @@ public class PowerFxREPL // Allow repl to create new UserDefinedFunctions. public bool AllowUserDefinedFunctions { get; set; } + /// + /// Enable the Import() function for importing modules. + /// Defaults to false. + /// + [Obsolete("preview")] + public bool AllowImport { 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; @@ -76,6 +83,11 @@ public class PowerFxREPL /// public ReadOnlySymbolValues ExtraSymbolValues { get; set; } + // Map from Module full path to Module. + private readonly Dictionary _loadedModules = new Dictionary(StringComparer.OrdinalIgnoreCase); + + internal IEnumerable Modules => _loadedModules.Values; + /// /// Get sorted names of all functions. This includes functions from the as well as . /// @@ -94,6 +106,68 @@ public IEnumerable FunctionNames } } + // Get combined symbol values (extra, modules, etc) + public ReadOnlySymbolValues GetCombined() + { + // Combine with symbols from modules. + var list = new List(); + + foreach (var module in _loadedModules.Values) + { + list.Add(module.Symbols); + } + + if (this.ExtraSymbolValues != null) + { + list.Add(this.ExtraSymbolValues.SymbolTable); + } + + var m1 = ReadOnlySymbolTable.Compose(list.ToArray()); + + var values = m1.CreateValues(this.ExtraSymbolValues); + + return values; + } + + internal bool TryResolveModule(string path, out Module module) + { + if (_loadedModules.TryGetValue(path, out module)) + { + return true; + } + + // can we resolve by short name? + List list = new List(); + + foreach (var m in _loadedModules.Values) + { + string shortName = System.IO.Path.GetFileName(m.FullPath); + if (string.Equals(shortName, path, StringComparison.OrdinalIgnoreCase)) + { + list.Add(m); + } + } + + if (list.Count == 1) + { + module = list[0]; + return true; + } + + return false; + } + + internal void DeleteModule(Module module) + { + _loadedModules.Remove(module.FullPath); + } + + internal void AddModule(Module module) + { + string id = module.FullPath; + _loadedModules[id] = module; + } + // Interpreter should normally not throw. // Exceptions should be caught and converted to ErrorResult. // Not called for OperationCanceledException since those are expected. @@ -123,6 +197,8 @@ public PowerFxREPL() this.MetaFunctions.AddFunction(new Help1Function(this)); } + private bool _finishInit = false; + private bool _userEnabled = false; public void EnableSampleUserObject() @@ -217,6 +293,22 @@ public async Task HandleLineAsync(string line, CancellationToken cancel = defaul } } + // Property ctor is run before Init properties are set. + // So apply final initialization after property initializers but before we execute commands. + private void FinishInit() + { + if (!_finishInit) + { + _finishInit = true; + if (this.AllowImport) + { + this.MetaFunctions.AddFunction(new ImportFunction(this)); + this.MetaFunctions.AddFunction(new ListModulesFunction(this)); + this.MetaFunctions.AddFunction(new DeleteModuleFunction(this)); + } + } + } + /// /// Directly invoke a command. This skips multiline handling. /// @@ -239,6 +331,8 @@ public virtual async Task HandleCommandAsync(string expression, Canc return new ReplResult(); } + FinishInit(); + if (this.Echo) { await this.WritePromptAsync(cancel); @@ -247,9 +341,10 @@ public virtual async Task HandleCommandAsync(string expression, Canc await this.Output.WriteLineAsync(expression.TrimEnd(), OutputKind.Repl, cancel); } - var extraSymbolTable = this.ExtraSymbolValues?.SymbolTable; + var extraValues = this.GetCombined(); + var extraSymbolTable = extraValues.SymbolTable; - var runtimeConfig = new RuntimeConfig(this.ExtraSymbolValues) + var runtimeConfig = new RuntimeConfig(extraValues) { ServiceProvider = new BasicServiceProvider(this.InnerServices) }; @@ -363,7 +458,7 @@ await this.Output.WriteLineAsync(lineError + error.ToString(), kind, cancel) // Get the type. var rhsExpr = declare._rhs.GetCompleteSpan().GetFragment(expression); - var setCheck = this.Engine.Check(rhsExpr, ParserOptions, this.ExtraSymbolValues?.SymbolTable); + var setCheck = this.Engine.Check(rhsExpr, ParserOptions, this.GetCombined().SymbolTable); if (!setCheck.IsSuccess) { await this.Output.WriteLineAsync($"Error: Failed to initialize '{name}'.", OutputKind.Error, cancel) diff --git a/src/strings/PowerFxResources.bg-BG.resx b/src/strings/PowerFxResources.bg-BG.resx index 1288eb2aab..fe46c42c9e 100644 --- a/src/strings/PowerFxResources.bg-BG.resx +++ b/src/strings/PowerFxResources.bg-BG.resx @@ -4114,6 +4114,10 @@ Наименуваната формула трябва да бъде израз. + + + Named type must be a type literal. + Дефинираната от потребителя функция трябва да има тяло. @@ -4331,6 +4335,10 @@ Не може да се делегира {0}: съдържа функция за поведение „{1}“. + + + CountRows може да върне кеширана стойност. Използвайте CountIf(DataSource, true), за да получите актуалната стойност за броя. + Определя дали посоченият текст съдържа съответствие с посочения формат. @@ -4752,4 +4760,12 @@ Неподдържан нетипизиран тип/тип преобразуване JSON „{0}“ в аргумента. + + При изследването на полезния обем в JSON бе достигната максималната дълбочина. + + + + Във функцията в JSON бе достигната максималната дължина. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.ca-ES.resx b/src/strings/PowerFxResources.ca-ES.resx index 04465d0075..d75f099c5a 100644 --- a/src/strings/PowerFxResources.ca-ES.resx +++ b/src/strings/PowerFxResources.ca-ES.resx @@ -4114,6 +4114,10 @@ La fórmula amb nom ha de ser una expressió. + + + Named type must be a type literal. + La funció definida per l'usuari ha de tenir un cos. @@ -4331,6 +4335,10 @@ No es pot delegar {0}: conté una funció de comportament "{1}". + + + CountRows pot tornar un valor emmagatzemat a la memòria cau. Utilitzeu CountIf(DataSource, true) per obtenir el recompte més recent. + Determina si el text proporcionat coincideix amb el format de text proporcionat. @@ -4752,4 +4760,12 @@ Tipus de conversió sense tipus/JSON no admès "{0}" a l'argument. + + S'ha assolit la profunditat màxima mentre es recorria la càrrega útil de JSON. + + + + S'ha assolit la longitud màxima a la funció JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.cs-CZ.resx b/src/strings/PowerFxResources.cs-CZ.resx index c681288c6c..22de1c6327 100644 --- a/src/strings/PowerFxResources.cs-CZ.resx +++ b/src/strings/PowerFxResources.cs-CZ.resx @@ -4114,6 +4114,10 @@ Pojmenovaný vzorec musí být výraz. + + + Named type must be a type literal. + Uživatelem definovaná funkce musí mít text. @@ -4331,6 +4335,10 @@ Nelze delegovat {0}: obsahuje funkci chování '{1}'. + + + CountRows může vrátit hodnotu uloženou v mezipaměti. Pomocí CountIf(DataSource, true) získáte nejnovější počet. + Určuje, jestli zadaný text obsahuje shodu zadaného formátu textu. @@ -4752,4 +4760,12 @@ Nepodporovaný typ konverze bez definovaného typu / JSON „{0}“ v argumentu. + + Při procházení datové části JSON bylo dosaženo maximální hloubky. + + + + Ve funkci JSON byla dosažena maximální délka. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.da-DK.resx b/src/strings/PowerFxResources.da-DK.resx index 8a40515b92..53c85ddb71 100644 --- a/src/strings/PowerFxResources.da-DK.resx +++ b/src/strings/PowerFxResources.da-DK.resx @@ -4114,6 +4114,10 @@ Den navngivne formel skal være et udtryk. + + + Named type must be a type literal. + Brugerdefineret funktion skal have en brødtekst. @@ -4331,6 +4335,10 @@ Kan ikke uddelegere {0}: indeholder en adfærdsfunktion '{1}'. + + + CountRows returnerer muligvis en cachelagret værdi. Brug CountIf(DataSource, true) for at hente den seneste optælling. + Fastslår, om den angivne tekst har en forekomst af det angivne tekstformat. @@ -4752,4 +4760,12 @@ Ikke-understøttet, ikke-indtastet/JSON-konverteringstype '{0}' i argument. + + Maksimal dybde nået under gennemgang af JSON-nyttedata. + + + + Maksimal længde nået i JSON-funktion. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.de-DE.resx b/src/strings/PowerFxResources.de-DE.resx index 7d311a752d..fa2e123415 100644 --- a/src/strings/PowerFxResources.de-DE.resx +++ b/src/strings/PowerFxResources.de-DE.resx @@ -4114,6 +4114,10 @@ Die benannte Formel muss ein Ausdruck sein. + + + Named type must be a type literal. + Die benutzerdefinierte Funktion muss einen Textkörper aufweisen. @@ -4331,6 +4335,10 @@ {0} kann nicht delegiert werden: Enthält eine Verhaltensfunktion „{1}“. + + + CountRows gibt möglicherweise einen zwischengespeicherten Wert zurück. Verwenden Sie CountIf(DataSource, true), um die aktuelle Anzahl zu erhalten. + Ermittelt, ob der angegebene Text dem angegebenen Textformat entspricht. @@ -4752,4 +4760,12 @@ Nicht unterstützter nicht typisierter/JSON-Konvertierungstyp „{0}“ im Argument. + + Die maximale Tiefe, die beim Durchlaufen der JSON-Payload erreicht wird. + + + + Die maximale Länge in der JSON-Funktion wurde erreicht. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.el-GR.resx b/src/strings/PowerFxResources.el-GR.resx index 62d3433fdd..6acfaeb6ea 100644 --- a/src/strings/PowerFxResources.el-GR.resx +++ b/src/strings/PowerFxResources.el-GR.resx @@ -4114,6 +4114,10 @@ Ο τύπος με όνομα πρέπει να είναι παράσταση. + + + Named type must be a type literal. + Η συνάρτηση που ορίζεται από τον χρήστη πρέπει να έχει σώμα. @@ -4331,6 +4335,10 @@ Δεν είναι δυνατή η ανάθεση {0}: περιέχει μια συνάρτηση συμπεριφοράς '{1}'. + + + Το CountRows μπορεί να επιστρέψει μια τιμή στο cache. Χρησιμοποιήστε το CountIf(DataSource, true) για να λάβετε το πιο πρόσφατο πλήθος. + Προσδιορίζει αν το παρεχόμενο κείμενο διαθέτει αντιστοιχία με την παρεχόμενη μορφή κειμένου. @@ -4752,4 +4760,12 @@ Μη υποστηριζόμενος μη τυπολογημένος/JSON τύπος μετατροπής '{0}' στο όρισμα. + + Το μέγιστο βάθος που επιτεύχθηκε κατά τη διέλευση ωφέλιμου φορτίου JSON. + + + + Το μέγιστο μήκος που επιτεύχθηκε σε συνάρτηση JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.en-US.resx b/src/strings/PowerFxResources.en-US.resx index 84509cbaf5..a745ad5abb 100644 --- a/src/strings/PowerFxResources.en-US.resx +++ b/src/strings/PowerFxResources.en-US.resx @@ -3621,6 +3621,18 @@ https://go.microsoft.com/fwlink/?linkid=2132570 {Locked} + + Behavior function in a non-behavior user-defined function (UDF). Please wrap the user-defined function body with curly braces ({...}) to declare a behavior UDF. + Error Message. + + + Behavior functions change the current session state. 'Clear', 'Collect', 'Patch', and 'Refresh' are common behavior functions. + {Locked=Clear}{Locked=Collect}{Locked=Patch}{Locked=Refresh} + + + Edit the user-defined function expression so that it is inside curly braces ({...}). + 1 How to fix the error. + Test function in a non-test property. You can't use this property to invoke test-only functions. Error Message. The term 'Test' is an adjective ('Test function' = 'function for testing'). @@ -4014,6 +4026,60 @@ https://go.microsoft.com/fwlink/?linkid=2132702 {Locked} + + + Delegation warning. The named formula {0} is not delegable so its usage in this formula might not work correctly on large data sets. + Error Message. + + + The data source might not be able to process the formula and might return an incomplete data set. Your application might not return correct results or behave correctly if the data set is incomplete. + + + Try simplifying the definition for the named formula. + 1 How to fix the error. + + + Article: Understand delegation in a canvas app + Article on delegation + + + https://go.microsoft.com/fwlink/?linkid=2132701 + {Locked} + + + Blog: Data row limits for delegation + Blog: Data row limits for delegation + + + https://go.microsoft.com/fwlink/?linkid=2132702 + {Locked} + + + Delegation warning. The user-defined function {0} is not delegable so its usage in this formula might not work correctly on large data sets. + Error Message. + + + The data source might not be able to process the formula and might return an incomplete data set. Your application might not return correct results or behave correctly if the data set is incomplete. + + + Try simplifying the definition for the user-defined function. + 1 How to fix the error. + + + Article: Understand delegation in a canvas app + Article on delegation + + + https://go.microsoft.com/fwlink/?linkid=2132701 + {Locked} + + + Blog: Data row limits for delegation + Blog: Data row limits for delegation + + + https://go.microsoft.com/fwlink/?linkid=2132702 + {Locked} Delegation warning. The highlighted part of this formula might not work correctly on large data sets. The "{0}" operation is not supported by this connector. @@ -4115,6 +4181,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 @@ -4757,15 +4831,19 @@ Error Message shown to user when a value other name or type literal is passed into AsType, IsType and ParseJSON functions. - Unsupported untyped/JSON conversion type '{0}' in argument. + Unsupported type '{0}' in type 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 {Locked=JSON} function when a document that is too deeply nested is passed to it. The term JSON refers to the data format described in www.json.org. + 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. The term JSON refers to the data format described in www.json.org. - + Error message returned by the {Locked=JSON} function when the result generated by this function would be too long. + + + The 'User-defined types' experimental feature is not enabled. + Error message returned when user uses User-defined types with the User-defined types feature flag turned off. + \ No newline at end of file diff --git a/src/strings/PowerFxResources.es-ES.resx b/src/strings/PowerFxResources.es-ES.resx index 4c1a8b486d..6722d170cd 100644 --- a/src/strings/PowerFxResources.es-ES.resx +++ b/src/strings/PowerFxResources.es-ES.resx @@ -4114,6 +4114,10 @@ La fórmula con nombre debe ser una expresión. + + + Named type must be a type literal. + La función definida por el usuario debe tener un cuerpo. @@ -4331,6 +4335,10 @@ No se puede delegar {0}: contiene una función de comportamiento "{1}". + + + CountRows puede devolver un valor guardado en caché. Utilice CountIf(DataSource, true) para obtener el recuento más reciente. + Determina si el texto proporcionado coincide con el formato de texto proporcionado. @@ -4752,4 +4760,12 @@ Tipo de conversión de sin tipo / JSON "{0}" en el argumento. + + Profundidad máxima alcanzada al recorrer la carga de JSON. + + + + Longitud máxima alcanzada en una función JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.et-EE.resx b/src/strings/PowerFxResources.et-EE.resx index 0afe9e6943..8041f75e2b 100644 --- a/src/strings/PowerFxResources.et-EE.resx +++ b/src/strings/PowerFxResources.et-EE.resx @@ -4114,6 +4114,10 @@ Nimega valem peab olema avaldis. + + + Named type must be a type literal. + Kasutaja määratletud funktsioonil peab olema sisu. @@ -4331,6 +4335,10 @@ Üksust {0} ei saa delegeerida: sisaldab käitumisfunktsiooni „{1}“. + + + CountRows võib tagastada vahemällu salvestatud väärtuse. Uusima arvu toomiseks kasutage parameetrit CountIf(DataSource, true). + Määrab, kas esitatud tekstil esineb esitatud tekstivormingu vaste. @@ -4752,4 +4760,12 @@ Toetuseta tüübita/JSON-i teisenduse tüüp „{0}“ argumendis. + + JSON-i lasti läbimisel on saavutatud sügavuse maksimum. + + + + JSON-i funktsioonis on jõutud pikkuse ülempiirini. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.eu-ES.resx b/src/strings/PowerFxResources.eu-ES.resx index bfb5755828..6cb2de9a6f 100644 --- a/src/strings/PowerFxResources.eu-ES.resx +++ b/src/strings/PowerFxResources.eu-ES.resx @@ -4114,6 +4114,10 @@ Izendatutako formulak adierazpen bat izan behar du. + + + Named type must be a type literal. + Erabiltzaileak definitutako funtzioak gorputz bat izan behar du. @@ -4331,6 +4335,10 @@ Ezin da eskuordetu {0}: "{1}" portaera-funtzioa dauka. + + + Baliteke CountRows-ek cachean gordetako balio bat itzultzea. Erabili CountIf(DataSource, true) azken zenbaketa lortzeko. + Emandako testua eta testuaren formatua bat datozen zehazten du. @@ -4752,4 +4760,12 @@ Argumentuan "{0}" motako onartu gabeko motarik gabeko/JSON bihurketa + + Gehieneko sakonerara iritsi da JSON karga aztertu bitartean. + + + + Gehieneko luzerara iritsi da JSON funtzioan. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.fi-FI.resx b/src/strings/PowerFxResources.fi-FI.resx index b285f8d632..1256f9f19d 100644 --- a/src/strings/PowerFxResources.fi-FI.resx +++ b/src/strings/PowerFxResources.fi-FI.resx @@ -4114,6 +4114,10 @@ Nimetyn kaavan on oltava lauseke. + + + Named type must be a type literal. + Käyttäjän määrittämässä funktiossa on oltava teksti. @@ -4331,6 +4335,10 @@ Ei voida delegoida kohdetta {0}: kohde sisältää käyttäytymisfunktion {1}. + + + CountRows voi palauttaa välimuistiin tallennetun arvon. Ota käyttöön CountIf(DataSource, true), jos haluat saada uusimman arvon. + Määrittää, onko annetulla tekstillä annetun tekstimuodon vastaavuus. @@ -4752,4 +4760,12 @@ Tukematon tyypittämätön/JSON-muunnoksen tyyppi {0} argumentissa. + + Enimmäissyvyys saavutettiin kuljettaessa JSON-tietojen läpi. + + + + Enimmäispituus saavutettiin JSON-funktiossa. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.fr-FR.resx b/src/strings/PowerFxResources.fr-FR.resx index 8747a0ae10..7f2015e2a4 100644 --- a/src/strings/PowerFxResources.fr-FR.resx +++ b/src/strings/PowerFxResources.fr-FR.resx @@ -4114,6 +4114,10 @@ La formule nommée doit être une expression. + + + Named type must be a type literal. + La fonction définie par l’utilisateur doit avoir un corps. @@ -4331,6 +4335,10 @@ Impossible de déléguer {0} : contient une fonction de comportement « {1} ». + + + CountRows peut renvoyer une valeur en cache. Utilisez CountIf(DataSource, true) pour obtenir le dernier décompte. + Détermine si le texte fourni a une correspondance avec le format texte fourni. @@ -4752,4 +4760,12 @@ Type de conversion sans type/JSON non pris en charge « {0} » dans l’argument. + + Profondeur maximale atteinte lors du parcours de la marge utile JSON. + + + + Longueur maximale atteinte dans la fonction JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.gl-ES.resx b/src/strings/PowerFxResources.gl-ES.resx index a030573a22..c0e4dbe978 100644 --- a/src/strings/PowerFxResources.gl-ES.resx +++ b/src/strings/PowerFxResources.gl-ES.resx @@ -4114,6 +4114,10 @@ A fórmula con nome debe ser unha expresión. + + + Named type must be a type literal. + A función definida polo usuario debe ter un corpo. @@ -4331,6 +4335,10 @@ Non se pode delegar {0}: contén unha función de comportamento "{1}". + + + É posible que CountRows devolva un valor almacenado na caché. Use CountIf(DataSource, true) para obter o reconto máis recente. + Determina se o texto fornecido coincide co formato de texto fornecido. @@ -4752,4 +4760,12 @@ Tipo de conversión sen tipo ou JSON "{0}" non compatible no argumento. + + Alcanzouse a profundidade máxima ao percorrer a carga de JSON. + + + + Alcanzouse a lonxitude máxima na función JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.hi-IN.resx b/src/strings/PowerFxResources.hi-IN.resx index e0f955d87f..dab73387a0 100644 --- a/src/strings/PowerFxResources.hi-IN.resx +++ b/src/strings/PowerFxResources.hi-IN.resx @@ -4114,6 +4114,10 @@ नामित सूत्र एक व्यंजक होना चाहिए. + + + Named type must be a type literal. + उपयोगकर्ता-परिभाषित फ़ंक्शन में एक बॉडी होनी चाहिए. @@ -4331,6 +4335,10 @@ {0} को प्रतिनिधि नहीं कर सकते हैं: एक व्यवहार फ़ंक्‍शन '{1}' शामिल है. + + + CountRows कैश्ड मान लौटा सकती है. नवीनतम गणना प्राप्त करने के लिए CountIf(DataSource, true) का उपयोग करें. + निर्धारित करता है कि प्रदत्त पाठ, प्रदत्त पाठ स्वरूप से मेल खाता है. @@ -4752,4 +4760,12 @@ तर्क में असमर्थित अनटाइप्ड/JSON रूपांतरण प्रकार '{0}'. + + JSON पेलोड को ट्रैवर्स करते समय अधिकतम गहराई तक पहुँच गया. + + + + JSON फ़ंक्शन में अधिकतम लंबाई पहुँच गई. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.hr-HR.resx b/src/strings/PowerFxResources.hr-HR.resx index 95b5e807af..7c3d68cdde 100644 --- a/src/strings/PowerFxResources.hr-HR.resx +++ b/src/strings/PowerFxResources.hr-HR.resx @@ -1856,7 +1856,7 @@ - Netipizirana vrijednost za konverziju kao novi tip. + Netipizirana vrijednost za konverziju kao nova vrsta. Vrstu za koju želimo koristiti određenu vrijednost. @@ -4114,6 +4114,10 @@ Imenovana formula mora biti izraz. + + + Named type must be a type literal. + Korisnički definirana funkcija mora imati tijelo. @@ -4331,6 +4335,10 @@ {0} se ne može delegirati: sadrži funkciju ponašanja „{1}”. + + + CountRows može vratiti predmemoriranu vrijednost. Upotrijebite CountIf(DataSource, true) za dobivanje posljednjeg zbroja. + Utvrđuje podudara li se navedeni tekst s navedenim oblikom teksta. @@ -4752,4 +4760,12 @@ Nepodržana netipizirana/JSON vrsta pretvorbe '{0}' u argumentu. + + Dosegnuta je maksimalna dubina tijekom prelaska korisnih podataka JSON. + + + + Dosegnuta je maksimalna duljina u funkciji JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.hu-HU.resx b/src/strings/PowerFxResources.hu-HU.resx index 7cbc9032a4..eb516b9527 100644 --- a/src/strings/PowerFxResources.hu-HU.resx +++ b/src/strings/PowerFxResources.hu-HU.resx @@ -4114,6 +4114,10 @@ A megnevezett képletnek kifejezésnek kell lennie. + + + Named type must be a type literal. + A felhasználó által definiált függvénynek rendelkeznie kell egy törzzsel. @@ -4331,6 +4335,10 @@ A(z) {0} nem delegálható: '{1}' viselkedési függvényt tartalmaz. + + + A CountRows gyorsítótárazott értéket adhat vissza. A legújabb mennyiség beolvasása érdekében használja a CountIf(DataSource, true) függvényt. + Azt határozza meg, hogy a megadott szöveg megfelel-e a megadott szövegformátumnak. @@ -4752,4 +4760,12 @@ Nem támogatott típus nélküli/JSON átalakítási típus a(z) „{0}” argumentumban. + + Elérte a maximális mélységet a JSON hasznos adat végigjárása során. + + + + Elérte a JSON függvény maximális hosszát. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.id-ID.resx b/src/strings/PowerFxResources.id-ID.resx index 557cc619c2..119d8e9ea6 100644 --- a/src/strings/PowerFxResources.id-ID.resx +++ b/src/strings/PowerFxResources.id-ID.resx @@ -4114,6 +4114,10 @@ Rumus bernama harus merupakan ekspresi. + + + Named type must be a type literal. + Fungsi yang ditentukan pengguna harus memiliki isi. @@ -4331,6 +4335,10 @@ Tidak dapat mendelegasikan {0}: berisi fungsi perilaku '{1}'. + + + CountRows dapat mengembalikan nilai cache. Gunakan CountIf(DataSource, true) untuk mendapatkan hitungan terbaru. + Menentukan apakah teks yang diberikan sesuai dengan format teks yang ada. @@ -4752,4 +4760,12 @@ Jenis konversi '{0}' tidak berjenis/JSON yang tidak didukung dalam argumen. + + Kedalaman maksimum yang dicapai saat mengunjungi payload JSON. + + + + Panjang maksimum yang dicapai dalam fungsi JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.it-IT.resx b/src/strings/PowerFxResources.it-IT.resx index b10ac10468..97a36d4026 100644 --- a/src/strings/PowerFxResources.it-IT.resx +++ b/src/strings/PowerFxResources.it-IT.resx @@ -4114,6 +4114,10 @@ La formula denominata deve essere un'espressione. + + + Named type must be a type literal. + La funzione definita dall'utente deve avere un corpo. @@ -4331,6 +4335,10 @@ Impossibile delegare {0}: contiene una funzione di comportamento "{1}". + + + CountRows può restituire un valore memorizzato nella cache. Usa CountIf(DataSource, true) per ottenere il conteggio più recente. + Determina se per il testo specificato viene trovata una corrispondenza del formato di testo indicato. @@ -4752,4 +4760,12 @@ Tipo di conversione JSON/non tipizzata non supportato "{0}" nell'argomento. + + Profondità massima raggiunta durante l'attraversamento del payload JSON. + + + + Lunghezza massima raggiunta nella funzione JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.ja-JP.resx b/src/strings/PowerFxResources.ja-JP.resx index 5498b0b6d5..cfcbe654e2 100644 --- a/src/strings/PowerFxResources.ja-JP.resx +++ b/src/strings/PowerFxResources.ja-JP.resx @@ -4114,6 +4114,10 @@ 名前付き計算式は式である必要があります。 + + + Named type must be a type literal. + ユーザー定義関数には本文が必要です。 @@ -4331,6 +4335,10 @@ {0} を委任できません: 動作機能 '{1}' を含みます。 + + + CountRows はキャッシュされた値を返す場合があります。最新の件数を取得する際は CountIf(DataSource, true) を使用します。 + 指定されたテキストが指定されたテキスト形式に一致しているかどうかを判定します。 @@ -4752,4 +4760,12 @@ 引数に存在する型指定されていない/JSON 変換型 '{0}' には対応していません。 + + JSON ペイロードを移動する際に深さの上限に到達しました。 + + + + JSON 関数で長さの上限に達しました。 + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.kk-KZ.resx b/src/strings/PowerFxResources.kk-KZ.resx index a1d2a73bec..5544196e28 100644 --- a/src/strings/PowerFxResources.kk-KZ.resx +++ b/src/strings/PowerFxResources.kk-KZ.resx @@ -4114,6 +4114,10 @@ Аталған формула өрнек болуы керек. + + + Named type must be a type literal. + Пайдаланушы анықтайтын функцияның негізгі мәтіні болуы керек. @@ -4331,6 +4335,10 @@ {0} сенім білдіру мүмкін емес: құрамында "{1}" тәртіптік функциясы бар. + + + CountRows кэштелген мәнді қайтаруы мүмкін. Соңғы санды алу үшін CountIf(DataSource, шын) пайдаланыңыз. + Берілген мәтіннің берілген мәтін пішіміне сәйкестігін анықтайды. @@ -4752,4 +4760,12 @@ Аргументтегі '{0}' терілмеген/JSON түрлендіру түріне қолдау көрсетілмейді. + + JSON пайдалы деректерін қарап шығу кезінде максималды тереңдікке жетті. + + + + JSON функциясында максималды ұзындыққа жетті. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.ko-KR.resx b/src/strings/PowerFxResources.ko-KR.resx index a38d4609b1..afe88f2001 100644 --- a/src/strings/PowerFxResources.ko-KR.resx +++ b/src/strings/PowerFxResources.ko-KR.resx @@ -4114,6 +4114,10 @@ 이름이 지정된 수식은 식이어야 합니다. + + + Named type must be a type literal. + 사용자 정의 함수에는 본문이 있어야 합니다. @@ -4331,6 +4335,10 @@ {0}을(를) 위임할 수 없음: 동작 함수 '{1}'을(를) 포함합니다. + + + CountRows는 캐시된 값을 반환할 수 있습니다. CountIf(DataSource, true)를 사용하여 최신 카운트를 가져옵니다. + 입력한 텍스트가 입력한 텍스트 형식과 일치하는지 결정합니다. @@ -4752,4 +4760,12 @@ 인수에 미지원 형식화되지 않은/JSON 변환 유형 '{0}'이(가) 있습니다. + + JSON 페이로드를 트래버스하는 동안 최대 깊이에 도달했습니다. + + + + JSON 함수의 최대 길이에 도달했습니다. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.lt-LT.resx b/src/strings/PowerFxResources.lt-LT.resx index 964c0a8b79..bd493c8813 100644 --- a/src/strings/PowerFxResources.lt-LT.resx +++ b/src/strings/PowerFxResources.lt-LT.resx @@ -4114,6 +4114,10 @@ Įvardytoji formulė turi būti reiškinys. + + + Named type must be a type literal. + Vartotojo apibrėžtoje funkcijoje turi būti teksto. @@ -4331,6 +4335,10 @@ Negalima perduoti {0}: yra veikimo funkcija „{1}“. + + + CountRows gali pateikti talpykloje saugomą reikšmę. Naudokite CountIf(DataSource, true), kad gautumėte naujausią skaičių. + Nustatoma, ar yra pateikto teksto atitikmuo pateikto teksto formatu. @@ -4752,4 +4760,12 @@ Argumente yra nepalaikomas neapibrėžtas / JSON konvertavimo tipas {0}. + + Didžiausias gylis pasiektas perskiriant JSON paketo turinį. + + + + Didžiausias ilgis pasiektas JSON funkcijoje. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.lv-LV.resx b/src/strings/PowerFxResources.lv-LV.resx index dcbf3fa800..7c92429d07 100644 --- a/src/strings/PowerFxResources.lv-LV.resx +++ b/src/strings/PowerFxResources.lv-LV.resx @@ -4114,6 +4114,10 @@ Nosauktajai formulai ir jābūt izteiksmei. + + + Named type must be a type literal. + Lietotāja definētai funkcijai ir jābūt pamattekstam. @@ -4331,6 +4335,10 @@ Nevar deleģēt {0}: satur uzvedības funkciju '{1}'. + + + CountRows var atgriezt kešatmiņā saglabāto vērtību. Izmantojiet CountIf(DataSource, true), lai iegūtu jaunāko skaitu. + Nosaka, vai norādītais teksts atbilst norādītajam teksta formātam. @@ -4752,4 +4760,12 @@ Argumentā ir neatbalstīts netipizēts/JSON pārvēršanas tips “{0}”. + + Maksimālais dziļums, kas sasniegts, pārvietojot JSON vērtumu. + + + + Sasniegts JSON funkcijas maksimālais garums. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.ms-MY.resx b/src/strings/PowerFxResources.ms-MY.resx index 7bb53eff53..1a1e71a03f 100644 --- a/src/strings/PowerFxResources.ms-MY.resx +++ b/src/strings/PowerFxResources.ms-MY.resx @@ -4114,6 +4114,10 @@ Formula yang dinamakan mestilah ungkapan. + + + Named type must be a type literal. + Fungsi ditakrifkan pengguna mesti mempunyai isi. @@ -4331,6 +4335,10 @@ Tidak dapat menugaskan {0}: mengandungi fungsi kelakuan '{1}'. + + + CountRows mungkin mengembalikan nilai cache. Gunakan CountIf(DataSource, true) untuk mendapatkan kiraan terkini. + Menentukan sama ada teks yang dibekalkan sepadan dengan format teks yang dibekalkan atau tidak. @@ -4752,4 +4760,12 @@ Tanpa jenis/jenis penukaran JSON yang tidak disokong '{0}' dalam argumen. + + Kedalaman maksimum dicapai semasa menyusur muat beban JSON. + + + + Panjang maksimum dicapai dalam fungsi JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.nb-NO.resx b/src/strings/PowerFxResources.nb-NO.resx index 35ab085ad2..9b7760281a 100644 --- a/src/strings/PowerFxResources.nb-NO.resx +++ b/src/strings/PowerFxResources.nb-NO.resx @@ -4114,6 +4114,10 @@ Den navngitte formelen må være et uttrykk. + + + Named type must be a type literal. + Brukerdefinert funksjon må ha en brødtekst. @@ -4331,6 +4335,10 @@ Kan ikke delegere {0}: inneholder en virkemåtefunksjon {1}. + + + CountRows kan returnere en bufret verdi. Bruk CountIf(DataSource, true) for å få det nyeste antallet. + Avgjør om den angitte teksten har et samsvar med det angitte tekstformatet. @@ -4752,4 +4760,12 @@ Det er ikke støtte for typeløs/JSON-konverteringstype {0} i argumentet. + + Maksimal dybde nådd under traversering av JSON-nyttelast. + + + + Maksimal lengde nådd i JSON-funksjon. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.nl-NL.resx b/src/strings/PowerFxResources.nl-NL.resx index 5da10d88fa..7753437bfd 100644 --- a/src/strings/PowerFxResources.nl-NL.resx +++ b/src/strings/PowerFxResources.nl-NL.resx @@ -4114,6 +4114,10 @@ De genoemde formule moet een expressie zijn. + + + Named type must be a type literal. + Door de gebruiker gedefinieerde functie moet een hoofdtekst hebben. @@ -4331,6 +4335,10 @@ Kan {0} niet machtigen: bevat de gedragsfunctie {1}. + + + CountRows kan een waarde in cache retourneren. Gebruik CountIf(DataSource, true) om het meest recente aantal op te halen. + Bepaalt of er voor de opgegeven tekst een overeenkomst in de aangeleverde tekstindeling bestaat. @@ -4752,4 +4760,12 @@ Niet-ondersteund niet-getypeerd/JSON-conversietype '{0}' in argument. + + Maximumdiepte bereikt tijdens doorlopen van JSON-nettolading. + + + + Maximale lengte bereikt in JSON-functie. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.pl-PL.resx b/src/strings/PowerFxResources.pl-PL.resx index c6cf8eb5a3..0ba0a865da 100644 --- a/src/strings/PowerFxResources.pl-PL.resx +++ b/src/strings/PowerFxResources.pl-PL.resx @@ -4114,6 +4114,10 @@ Nazwana formuła musi być wyrażeniem. + + + Named type must be a type literal. + Funkcja zdefiniowana przez użytkownika musi mieć treść. @@ -4331,6 +4335,10 @@ Nie można wydelegować operacji {0}: zawiera funkcję zachowania „{1}”. + + + Wartość CountRows może zwracać buforowaną wartość. Użyj funkcji CountIf(DataSource, true), aby uzyskać najnowszą liczbę. + Określa, czy podany tekst ma odpowiednik w postaci formatu podanego tekstu. @@ -4752,4 +4760,12 @@ Nieobsługiwany typ konwersji beztypowy/JSON „{0}” w argumencie. + + Osiągnięto maksymalną głębokość podczas realizacji procesu przechodzenia w odniesieniu do ładunku JSON. + + + + Osiągnięto maksymalną długość w funkcji JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.pt-BR.resx b/src/strings/PowerFxResources.pt-BR.resx index 7d5c9fb464..c1afe37063 100644 --- a/src/strings/PowerFxResources.pt-BR.resx +++ b/src/strings/PowerFxResources.pt-BR.resx @@ -4114,6 +4114,10 @@ A fórmula nomeada deve ser uma expressão. + + + Named type must be a type literal. + A função definida pelo usuário deve ter um corpo. @@ -4331,6 +4335,10 @@ Não é possível delegar {0}: contém uma função de comportamento "{1}". + + + CountRows pode retornar um valor em cache. Use CountIf(DataSource, true) para obter a contagem mais recente. + Determina se o texto fornecido tem uma correspondência do formato de texto fornecido. @@ -4752,4 +4760,12 @@ Não há suporte ao tipo de conversão sem tipo/JSON '{0}' no argumento. + + Profundidade máxima atingida ao percorrer o conteúdo JSON. + + + + Tamanho máximo atingido na função JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.pt-PT.resx b/src/strings/PowerFxResources.pt-PT.resx index b21ff59259..eeb6728705 100644 --- a/src/strings/PowerFxResources.pt-PT.resx +++ b/src/strings/PowerFxResources.pt-PT.resx @@ -4114,6 +4114,10 @@ A fórmula com nome tem de ser uma expressão. + + + Named type must be a type literal. + A função definida pelo utilizador tem de ter um corpo. @@ -4331,6 +4335,10 @@ Não é possível delegar {0}: contém uma função de comportamento "{1}". + + + CountRows pode devolver um valor em cache. Utilize CountIf(DataSource, true) para obter a contagem mais recente. + Determina se o texto fornecido tem um correspondente do formato do texto fornecido. @@ -4752,4 +4760,12 @@ Tipo de conversão sem tipo/JSON não suportado "{0}" no argumento. + + Profundidade máxima alcançada ao percorrer o payload JSON. + + + + Comprimento máximo alcançado na função JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.ro-RO.resx b/src/strings/PowerFxResources.ro-RO.resx index 508026f83a..5c4b1e5757 100644 --- a/src/strings/PowerFxResources.ro-RO.resx +++ b/src/strings/PowerFxResources.ro-RO.resx @@ -4114,6 +4114,10 @@ Formula denumită trebuie să fie o expresie. + + + Named type must be a type literal. + Funcția definită de utilizator trebuie să includă un corp. @@ -4331,6 +4335,10 @@ Nu se poate delega {0}: conține o funcție de comportament „{1}”. + + + CountRows poate returna o valoare memorată în cache. Utilizați CountIf(DataSource, true) pentru a obține cea mai recentă contorizare. + Determină dacă în textul furnizat există o potrivire a formatului de text furnizat. @@ -4752,4 +4760,12 @@ În argument există tipul de conversie fără tip/JSON „{0}” neacceptat. + + Adâncimea maximă a fost atinsă la traversarea sarcinii JSON. + + + + Lungimea maximă a fost atinsă în funcția JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.ru-RU.resx b/src/strings/PowerFxResources.ru-RU.resx index 0f924f20b3..153e45f544 100644 --- a/src/strings/PowerFxResources.ru-RU.resx +++ b/src/strings/PowerFxResources.ru-RU.resx @@ -4114,6 +4114,10 @@ Именованная формула должна быть выражением. + + + Named type must be a type literal. + Пользовательская функция должна иметь тело. @@ -4331,6 +4335,10 @@ Невозможно делегировать "{0}": содержит функцию поведения "{1}". + + + CountRows может возвращать кэшированное значение. Для получения последнего количества используйте CountIf(DataSource, true). + Определяет, есть ли в предоставленном тексте совпадение с указанным форматом текста. @@ -4752,4 +4760,12 @@ Неподдерживаемый тип преобразования между нетипизированным значением и JSON "{0}" в аргументе. + + Достигнута максимальная глубина при обходе полезных данных JSON. + + + + Достигнута максимальная глубина в функции JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.sk-SK.resx b/src/strings/PowerFxResources.sk-SK.resx index 81ccebaa8c..2a7338d589 100644 --- a/src/strings/PowerFxResources.sk-SK.resx +++ b/src/strings/PowerFxResources.sk-SK.resx @@ -4114,6 +4114,10 @@ Pomenovaný vzorec musí byť výraz. + + + Named type must be a type literal. + Funkcia definovaná používateľom musí mať telo. @@ -4331,6 +4335,10 @@ {0} nie je možné delegovať: obsahuje funkciu správania {1}. + + + Funkcia CountRows môže vrátiť hodnotu uloženú vo vyrovnávacej pamäti. Na získanie najnovšieho počtu použite funkciu CountIf(DataSource, true). + Určuje, či zadaný text obsahuje zhodu zadaného formátu textu. @@ -4752,4 +4760,12 @@ Nepodporovaný typ konverzie netypový/JSON {0} v argumente. + + Pri prechádzaní údajovej časti JSON bola dosiahnutá maximálna hĺbka. + + + + Vo funkcii JSON bola dosiahnutá maximálna dĺžka. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.sl-SI.resx b/src/strings/PowerFxResources.sl-SI.resx index a9c0c4d854..8369dee0e8 100644 --- a/src/strings/PowerFxResources.sl-SI.resx +++ b/src/strings/PowerFxResources.sl-SI.resx @@ -4114,6 +4114,10 @@ Navedena formula mora biti izraz. + + + Named type must be a type literal. + Uporabniško določena funkcija mora imeti telo. @@ -4331,6 +4335,10 @@ Dodelitev za {0} ni mogoča: vsebuje funkcijo vedenja »{1}«. + + + CountRows lahko vrne predpomnjeno vrednost. Uporabite CountIf(DataSource, true), da dobite najnovejše število. + Določi, ali navedeno besedilo ustreza obliki zapisa navedenega besedila. @@ -4752,4 +4760,12 @@ Nepodprta neopredeljena vrsta/vrsta pretvorbe JSON »{0}« v argumentu. + + Največja globina, dosežena med sistematičnim iskanjem koristne vsebine JSON. + + + + Največja dosežena dolžina v funkciji JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.sr-Cyrl-RS.resx b/src/strings/PowerFxResources.sr-Cyrl-RS.resx index aa07a8765a..cb23624ca8 100644 --- a/src/strings/PowerFxResources.sr-Cyrl-RS.resx +++ b/src/strings/PowerFxResources.sr-Cyrl-RS.resx @@ -4114,6 +4114,10 @@ Именована формула мора да буде израз. + + + Named type must be a type literal. + Кориснички дефинисана функција мора да има тело. @@ -4331,6 +4335,10 @@ Није могуће делегирати {0}: садржи функцију понашања „{1}“. + + + CountRows може да врати кеширану вредност. Користите CountIf(DataSource, true) да бисте добили најновији број. + Утврђује да ли обезбеђени текст садржи подударање са обезбеђеним форматом текста. @@ -4752,4 +4760,12 @@ Неподржани нетипизирани/JSON тип конверзије ' {0}' у аргументу. + + Достигнута је максимална дубина приликом детаљног проласка JSON корисних података. + + + + Достигнута је максимална дужина у JSON функцији. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.sr-Latn-RS.resx b/src/strings/PowerFxResources.sr-Latn-RS.resx index 649988bbdc..ffcc5be019 100644 --- a/src/strings/PowerFxResources.sr-Latn-RS.resx +++ b/src/strings/PowerFxResources.sr-Latn-RS.resx @@ -4114,6 +4114,10 @@ Imenovana formula mora da bude izraz. + + + Named type must be a type literal. + Korisnički definisana funkcija mora da ima telo. @@ -4331,6 +4335,10 @@ Nije moguće delegirati {0}: sadrži funkciju ponašanja „{1}“. + + + CountRows može da vrati keširanu vrednost. Koristite CountIf(DataSource, true) da biste dobili najnoviji broj. + Utvrđuje da li obezbeđeni tekst sadrži podudaranje sa obezbeđenim formatom teksta. @@ -4752,4 +4760,12 @@ Nepodržani netipizirani/JSON tip konverzije '{0}' u argumentu. + + Dostignuta je maksimalna dubina prilikom detaljnog prolaska JSON korisnih podataka. + + + + Dostignuta je maksimalna dužina u JSON funkciji. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.sv-SE.resx b/src/strings/PowerFxResources.sv-SE.resx index 17de97ae9d..6a88f52bd3 100644 --- a/src/strings/PowerFxResources.sv-SE.resx +++ b/src/strings/PowerFxResources.sv-SE.resx @@ -4114,6 +4114,10 @@ Den namngivna formeln måste vara ett uttryck. + + + Named type must be a type literal. + En användardefinierad funktion måste ha en brödtext. @@ -4331,6 +4335,10 @@ Det går inte att delegera {0}: innehåller en beteendefunktion {1}. + + + CountRows kan returnera ett cachelagrat värde. Använd CountIf(DataSource, true) för att få det senaste värdet. + Fastställer om den angivna texten har en matchning för det angivna textformatet. @@ -4752,4 +4760,12 @@ I argumentet finns den typlösa/JSON-konverteringstypen {0} som inte stöds. + + Maximalt djup uppnått vid genomgång av JSON-nyttolast. + + + + Maximal längd uppnådd i JSON-funktion. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.th-TH.resx b/src/strings/PowerFxResources.th-TH.resx index 874567d6a1..fcf8e295db 100644 --- a/src/strings/PowerFxResources.th-TH.resx +++ b/src/strings/PowerFxResources.th-TH.resx @@ -4114,6 +4114,10 @@ สูตรที่มีชื่อต้องเป็นนิพจน์ + + + Named type must be a type literal. + ฟังก์ชันที่ผู้ใช้กำหนดต้องมีเนื้อหา @@ -4331,6 +4335,10 @@ ไม่สามารถมอบสิทธิ์ {0} ได้: มีฟังก์ชันการทำงาน '{1}' + + + CountRows อาจส่งคืนค่าที่เก็บไว้ในแคช ใช้ CountIf(DataSource, true) เพื่อรับจำนวนล่าสุด + กำหนดว่าข้อความที่ป้อนมีส่วนที่ตรงกับรูปแบบข้อความที่ป้อนหรือไม่ @@ -4752,4 +4760,12 @@ ชนิดการแปลงที่ไม่ได้ระบุชนิด/JSON '{0}' ที่ไม่รองรับในอาร์กิวเมนต์ + + ความลึกสูงสุดที่เข้าถึงขณ ะส่งผ่านส่วนข้อมูล JSON + + + + ถึงความยาวสูงสุดในฟังก์ชัน JSON แล้ว + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.tr-TR.resx b/src/strings/PowerFxResources.tr-TR.resx index a60cc107a8..57fa1ee211 100644 --- a/src/strings/PowerFxResources.tr-TR.resx +++ b/src/strings/PowerFxResources.tr-TR.resx @@ -4114,6 +4114,10 @@ Adlandırılmış formül bir ifade olmalı. + + + Named type must be a type literal. + Kullanıcı tanımlı işlevin gövdesi olmalıdır. @@ -4331,6 +4335,10 @@ {0} atanamıyor: '{1}' davranış işlevini içeriyor. + + + CountRows, önbelleğe alınmış bir değer geri döndürülebilir. En son sayımı almak için CountIf(DataSource, true) kullanın. + Sağlanan metnin, sağlanan metin biçimi ile eşleşip eşleşmediğini belirler. @@ -4752,4 +4760,12 @@ Bağımsız değişkende desteklenmeyen belirsiz tür/JSON dönüştürme türü '{0}'. + + JSON yükünde dolaşırken maksimum derinliğe ulaşıldı. + + + + JSON işlevinde maksimum uzunluğa ulaşıldı. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.uk-UA.resx b/src/strings/PowerFxResources.uk-UA.resx index 7c34f012f5..d2f95b065e 100644 --- a/src/strings/PowerFxResources.uk-UA.resx +++ b/src/strings/PowerFxResources.uk-UA.resx @@ -4114,6 +4114,10 @@ Іменована формула має бути виразом. + + + Named type must be a type literal. + У користувацької функції має бути тіло. @@ -4331,6 +4335,10 @@ Не вдалося делегувати {0}: містить поведінкову функцію "{1}". + + + Команда CountRows може повернути кешоване значення. Щоб отримати актуальну кількість, використовуйте команду CountIf(DataSource, true). + Визначає, чи вказаний текст відповідає вказаному текстовому формату. @@ -4752,4 +4760,12 @@ Непідтримуваний тип перетворення нетипізованих/JSON-даних '{0}' в аргументі. + + Під час обходження корисних даних JSON досягнуто максимальної глибини. + + + + Досягнуто максимальної довжини функції JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.vi-VN.resx b/src/strings/PowerFxResources.vi-VN.resx index f3acc2b43f..49645b0427 100644 --- a/src/strings/PowerFxResources.vi-VN.resx +++ b/src/strings/PowerFxResources.vi-VN.resx @@ -4114,6 +4114,10 @@ Công thức được đặt tên phải là một biểu thức. + + + Named type must be a type literal. + Hàm do người dùng xác định phải có nội dung. @@ -4331,6 +4335,10 @@ Không thể ủy quyền {0}: chứa hàm hành vi "{1}". + + + CountRows có thể trả về giá trị được lưu trong bộ đệm ẩn. Hãy dùng hàm CountIf(DataSource, true) để lấy số lượng mới nhất. + Xác định xem văn bản được cung cấp có khớp với định dạng văn bản được cung cấp không. @@ -4752,4 +4760,12 @@ Loại chuyển đổi JSON/chưa được phân loại '{0}' không được hỗ trợ trong đối số. + + Đã đạt đến độ sâu tối đa trong khi chuyển ngang tải trọng JSON. + + + + Đã đạt đến độ dài tối đa trong hàm JSON. + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.zh-CN.resx b/src/strings/PowerFxResources.zh-CN.resx index 1da3b8f095..50964edc83 100644 --- a/src/strings/PowerFxResources.zh-CN.resx +++ b/src/strings/PowerFxResources.zh-CN.resx @@ -4114,6 +4114,10 @@ 指定的公式必须为表达式。 + + + Named type must be a type literal. + 用户定义的函数必须有正文。 @@ -4331,6 +4335,10 @@ 无法委托 {0}: 包含行为函数 '{1}'。 + + + CountRows 可能会返回缓存的值。请使用 CountIf(DataSource, true) 获取最新计数。 + 确定提供的文本是否与所提供的文本格式匹配。 @@ -4752,4 +4760,12 @@ 参数中不支持非类型化/JSON 转换类型“{0}”。 + + 遍历 JSON 有效负载时达到最大深度。 + + + + 在 JSON 函数中达到最大长度。 + + \ No newline at end of file diff --git a/src/strings/PowerFxResources.zh-TW.resx b/src/strings/PowerFxResources.zh-TW.resx index b0e2c1643e..8dd3475ad7 100644 --- a/src/strings/PowerFxResources.zh-TW.resx +++ b/src/strings/PowerFxResources.zh-TW.resx @@ -4114,6 +4114,10 @@ 命名公式必須是運算式。 + + + Named type must be a type literal. + 使用者定義的函式必須有主體。 @@ -4331,6 +4335,10 @@ 無法委派 {0}: 包含行為函式 '{1}'。 + + + CountRows 可能會回傳快取值。使用 CountIf(DataSource, true) 取得最新的計數。 + 決定提供的文字是否與提供的格式相符。 @@ -4752,4 +4760,12 @@ 引數中有不支援的 untyped/JSON 轉換類型『{0}』。 + + 遍歷 JSON 承載時達到的最大深度。 + + + + 在 JSON 函式中達到的最大長度。 + + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs index be1000b0de..6a7a8c6e22 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/CompatibilityTests.cs @@ -33,22 +33,22 @@ public void CompatibilityTest() string text = (string)LoggingTestServer.GetFileText(@"Responses\Compatibility GetSchema.json"); - ConnectorType ctCdp = ConnectorFunction.GetConnectorTypeAndTableCapabilities(tableResolver, "name", "Schema/Items", StringValue.New(text), null, ConnectorCompatibility.CdpCompatibility, "dataset", out _, out _, out _); - ConnectorType ctPa = ConnectorFunction.GetConnectorTypeAndTableCapabilities(tableResolver, "name", "Schema/Items", StringValue.New(text), null, ConnectorCompatibility.PowerAppsCompatibility, "dataset", out _, out _, out _); - ConnectorType ctSw = ConnectorFunction.GetConnectorTypeAndTableCapabilities(tableResolver, "name", "Schema/Items", StringValue.New(text), null, ConnectorCompatibility.SwaggerCompatibility, "dataset", out _, out _, out _); + ConnectorType ctCdp = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.CdpCompatibility, "dataset", out _, out _, out _); + ConnectorType ctPa = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.PowerAppsCompatibility, "dataset", out _, out _, out _); + ConnectorType ctSw = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.SwaggerCompatibility, "dataset", out _, out _, out _); - string cdp = ctCdp.FormulaType._type.ToString(); - string pa = ctPa.FormulaType._type.ToString(); - string sw = ctSw.FormulaType._type.ToString(); + string cdp = ctCdp.FormulaType.ToStringWithDisplayNames(); + string pa = ctPa.FormulaType.ToStringWithDisplayNames(); + string sw = ctSw.FormulaType.ToStringWithDisplayNames(); // CDP compatibility: priority is an enum, when "format": "enum" isn't present - Assert.Equal("![Id1:s, Id3:s, Id4:s, priority:l, priority2:l]", cdp); + Assert.Equal("r![Id1`'User ID 1':s, Id3`'User ID 3':s, Id4`'User ID 4':s, priority`Priority:l, priority2`'Priority 2':l]", cdp); // Swagger compatibility: priority is a string as "format": "enum" isn't present - Assert.Equal("![Id1:s, Id3:s, Id4:s, priority:s, priority2:l]", sw); + Assert.Equal("r![Id1`'User ID 1':s, Id3`'User ID 3':s, Id4`'User ID 4':s, priority`Priority:s, priority2`'Priority 2':l]", sw); // PA compatibility: Id2 is internal and present (not the case for CDP/Swagger compatibilities) - Assert.Equal("![Id1:s, Id2:s, Id3:s, Id4:s, priority:s, priority2:l]", pa); + Assert.Equal("r![Id1`'User ID 1':s, Id2`'User ID 2':s, Id3`'User ID 3':s, Id4`'User ID 4':s, priority`Priority:s, priority2`'Priority 2':l]", pa); } } } diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/FileTabularConnector.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/FileTabularConnector.cs index 38468fc999..c015faaebd 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/FileTabularConnector.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/FileTabularConnector.cs @@ -8,6 +8,8 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerFx.Core; +using Microsoft.PowerFx.Core.Entities; using Microsoft.PowerFx.Types; using Xunit; using Xunit.Abstractions; @@ -41,7 +43,9 @@ public async Task FileTabularTest() // This one is not delegatable Assert.False(fileTable.IsDelegable); - Assert.Equal("*[line:s]", fileTable.Type._type.ToString()); + + // Lazy type + Assert.Equal("r*", fileTable.Type._type.ToString()); PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1); RecalcEngine engine = new RecalcEngine(config); @@ -59,7 +63,7 @@ public async Task FileTabularTest() StringValue str = Assert.IsType(result); Assert.Equal("b", str.Value); - RecordType trt = fileTable.TabularRecordType; + RecordType trt = fileTable.RecordType; Assert.NotNull(trt); } } @@ -73,17 +77,17 @@ public FileTabularService(string fileName) _fileName = File.Exists(fileName) ? fileName : throw new FileNotFoundException($"File not found: {_fileName}"); } - public override bool IsDelegable => false; - - public override ConnectorType ConnectorType => null; + public override bool IsDelegable => false; // No need for files public override HttpClient HttpClient => null; + internal override IReadOnlyDictionary Relationships => null; + // Initialization can be synchronous public void Init() { - SetRecordType(RecordType.Empty().Add("line", FormulaType.String)); + RecordType = new FileTabularRecordType(RecordType.Empty().Add("line", FormulaType.String)); } protected override async Task>> GetItemsInternalAsync(IServiceProvider serviceProvider, ODataParameters oDataParameters, CancellationToken cancellationToken) @@ -99,4 +103,45 @@ protected override async Task>> GetItems return lines.Select(line => DValue.Of(FormulaValue.NewRecordFromFields(new NamedValue("line", FormulaValue.New(line))))).ToArray(); } } + + internal class FileTabularRecordType : RecordType + { + internal readonly RecordType _recordType; + + public FileTabularRecordType(RecordType recordType) + : base(GetDisplayNameProvider(recordType), GetDelegationInfo()) + { + _recordType = recordType; + } + + private static TableDelegationInfo GetDelegationInfo() + { + return new CdpDelegationInfo() + { + TableName = "FileTabular" + }; + } + + private static DisplayNameProvider GetDisplayNameProvider(RecordType recordType) => DisplayNameProvider.New(recordType.FieldNames.Select(f => new KeyValuePair(new Core.Utils.DName(f), new Core.Utils.DName(f)))); + + public override bool TryGetFieldType(string name, out FormulaType type) + { + return _recordType.TryGetFieldType(name, out type); + } + + public override bool Equals(object other) + { + if (other == null || other is not FileTabularRecordType other2) + { + return false; + } + + return _recordType == other2._recordType; + } + + public override int GetHashCode() + { + throw new NotImplementedException(); + } + } } 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 86400c1585..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 @@ -270,6 +270,8 @@ + + diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenAITests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenAITests.cs index 345eb0e0cc..075814209f 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenAITests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/OpenAITests.cs @@ -152,6 +152,12 @@ public async Task OpenAI_CreateImageVariation2() private static BlobValue GetBlobFromFile(string file, bool base64) { + if (!Path.IsPathRooted(file)) + { + string root = Directory.GetCurrentDirectory(); + file = Path.Combine(root, file); + } + byte[] bytes = File.ReadAllBytes(file); return new BlobValue(new ByteArrayBlob(bytes)); } diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs index 5af9ef0b30..94077ffa0c 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs @@ -21,6 +21,7 @@ using Microsoft.PowerFx.Core.Types; using Microsoft.PowerFx.Functions; using Microsoft.PowerFx.Intellisense; +using Microsoft.PowerFx.Syntax; using Microsoft.PowerFx.Types; using Xunit; using Xunit.Abstractions; @@ -1382,30 +1383,24 @@ public async Task SQL_GetRelationships() var config = new PowerFxConfig(Features.PowerFxV1); using var httpClient = new HttpClient(testConnector); - string jwt = "eyJ0eXAi..."; - using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", "29941b77eb0a40fe925cd7a03cb85b40", () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + string jwt = "eyJ0eXAiO..."; + using var client = new PowerPlatformConnectorClient("4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f.08.common.tip1.azure-apihub.net", "4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f", "53f515b50c3e4925803ec1f0945e799f", () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; config.AddActionConnector(new ConnectorSettings("SQL") { IncludeInternalFunctions = true, AllowUnsupportedFunctions = true }, apiDoc, new ConsoleLogger(_output)); RecalcEngine engine = new RecalcEngine(config); RuntimeConfig rc = new RuntimeConfig().AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); - // We can't execute a query like this for unknown reasons so we'll have to do it manually - //string query = - // "SELECT fk.name 'FK Name', tp.name 'Parent table', cp.name, tr.name 'Refrenced table', cr.name " + - // "FROM sys.foreign_keys fk " + - // "INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id " + - // "INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id " + - // "INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id " + - // "INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id " + - // "INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id " + - // "ORDER BY tp.name, cp.column_id"; - - // This will return 4 tables in an Untyped Object string query = - "select name, object_id, parent_object_id, referenced_object_id from sys.foreign_keys; " + - "select object_id, name from sys.tables; " + - "select constraint_object_id, parent_column_id, parent_object_id, referenced_column_id, referenced_object_id from sys.foreign_key_columns; " + - "select name, object_id, column_id from sys.columns"; + "SELECT fk.name AS FK_Name, '[' + sp.name + '].[' + tp.name + ']' AS Parent_Table, cp.name AS Parent_Column, '[' + sr.name + '].[' + tr.name + ']' AS Referenced_Table, cr.name AS Referenced_Column" + + @" FROM sys.foreign_keys fk" + + @" INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id" + + @" INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id" + + @" INNER JOIN sys.schemas sp on tp.schema_id = sp.schema_id" + + @" INNER JOIN sys.schemas sr on tr.schema_id = sr.schema_id" + + @" INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id" + + @" INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id" + + @" INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id" + + @" WHERE '[' + sp.name + '].[' + tp.name + ']' = '[SalesLT].[Product]'"; testConnector.SetResponseFromFile(@"Responses\SQL GetRelationships SampleDB.json"); var result = await engine.EvalAsync(@$"SQL.ExecutePassThroughNativeQueryV2(""pfxdev-sql.database.windows.net"", ""SampleDB"", {{ query: ""{query}"" }})", CancellationToken.None, new ParserOptions() { AllowsSideEffects = true }, runtimeConfig: rc); @@ -1414,73 +1409,40 @@ public async Task SQL_GetRelationships() JsonUntypedObject juo = Assert.IsType(uov.Impl); JsonElement je = juo._element; - Result r = je.Deserialize(); - - SqlForeignKey[] fkt = r.ResultSets.Table1; - SqlTable[] tt = r.ResultSets.Table2; - SqlForeignKeyColumn[] fkct = r.ResultSets.Table3; - SqlColumn[] ct = r.ResultSets.Table4; + RelationshipResult r = je.Deserialize(); + var relationships = r.ResultSets.Table1; List sqlRelationShips = new List(); - foreach (SqlForeignKey fk in fkt) + foreach (var fk in relationships) { - foreach (SqlTable tp in tt.Where(tp => fk.parent_object_id == tp.object_id)) + sqlRelationShips.Add(new SqlRelationship() { - foreach (SqlTable tr in tt.Where(tr => fk.referenced_object_id == tr.object_id)) - { - foreach (SqlForeignKeyColumn fkc in fkct.Where(fkc => fkc.constraint_object_id == fk.object_id)) - { - foreach (SqlColumn cp in ct.Where(cp => fkc.parent_column_id == cp.column_id && fkc.parent_object_id == cp.object_id)) - { - foreach (SqlColumn cr in ct.Where(cr => fkc.referenced_column_id == cr.column_id && fkc.referenced_object_id == cr.object_id)) - { - sqlRelationShips.Add(new SqlRelationship() - { - RelationshipName = fk.name, - ParentTable = tp.name, - ColumnName = cp.name, - ReferencedTable = tr.name, - ReferencedColumnName = cr.name, - ColumnId = cp.column_id - }); - } - } - } - } - } + RelationshipName = fk.FK_Name, + ParentTable = fk.Parent_Table, + ColumnName = fk.Parent_Column, + ReferencedTable = fk.Referenced_Table, + ReferencedColumnName = fk.Referenced_Column + }); } - sqlRelationShips = sqlRelationShips.OrderBy(sr => sr.ParentTable).ThenBy(sr => sr.ColumnId).ToList(); - - Assert.Equal(12, sqlRelationShips.Count); - Assert.Equal("FK_CustomerAddress_Customer_CustomerID, CustomerAddress, CustomerID, Customer, CustomerID", sqlRelationShips[0].ToString()); - Assert.Equal("FK_CustomerAddress_Address_AddressID, CustomerAddress, AddressID, Address, AddressID", sqlRelationShips[1].ToString()); - Assert.Equal("FK_Product_ProductCategory_ProductCategoryID, Product, ProductCategoryID, ProductCategory, ProductCategoryID", sqlRelationShips[2].ToString()); - Assert.Equal("FK_Product_ProductModel_ProductModelID, Product, ProductModelID, ProductModel, ProductModelID", sqlRelationShips[3].ToString()); - Assert.Equal("FK_ProductCategory_ProductCategory_ParentProductCategoryID_ProductCategoryID, ProductCategory, ParentProductCategoryID, ProductCategory, ProductCategoryID", sqlRelationShips[4].ToString()); - Assert.Equal("FK_ProductModelProductDescription_ProductModel_ProductModelID, ProductModelProductDescription, ProductModelID, ProductModel, ProductModelID", sqlRelationShips[5].ToString()); - Assert.Equal("FK_ProductModelProductDescription_ProductDescription_ProductDescriptionID, ProductModelProductDescription, ProductDescriptionID, ProductDescription, ProductDescriptionID", sqlRelationShips[6].ToString()); - Assert.Equal("FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID, SalesOrderDetail, SalesOrderID, SalesOrderHeader, SalesOrderID", sqlRelationShips[7].ToString()); - Assert.Equal("FK_SalesOrderDetail_Product_ProductID, SalesOrderDetail, ProductID, Product, ProductID", sqlRelationShips[8].ToString()); - Assert.Equal("FK_SalesOrderHeader_Customer_CustomerID, SalesOrderHeader, CustomerID, Customer, CustomerID", sqlRelationShips[9].ToString()); - Assert.Equal("FK_SalesOrderHeader_Address_ShipTo_AddressID, SalesOrderHeader, ShipToAddressID, Address, AddressID", sqlRelationShips[10].ToString()); - Assert.Equal("FK_SalesOrderHeader_Address_BillTo_AddressID, SalesOrderHeader, BillToAddressID, Address, AddressID", sqlRelationShips[11].ToString()); - - string expected = @$"POST https://firstrelease-003.azure-apihub.net/invoke - authority: firstrelease-003.azure-apihub.net + Assert.Equal(2, sqlRelationShips.Count); + Assert.Equal("FK_Product_ProductModel_ProductModelID, [SalesLT].[Product], ProductModelID, [SalesLT].[ProductModel], ProductModelID", sqlRelationShips[0].ToString()); + Assert.Equal("FK_Product_ProductCategory_ProductCategoryID, [SalesLT].[Product], ProductCategoryID, [SalesLT].[ProductCategory], ProductCategoryID", sqlRelationShips[1].ToString()); + + string expected = @$"POST https://4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f.08.common.tip1.azure-apihub.net/invoke + authority: 4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f.08.common.tip1.azure-apihub.net Authorization: Bearer {jwt} path: /invoke scheme: https - x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/49970107-0806-e5a7-be5e-7c60e2750f01 + x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f x-ms-client-session-id: 8e67ebdc-d402-455a-b33a-304820832383 x-ms-request-method: POST - x-ms-request-url: /apim/sql/29941b77eb0a40fe925cd7a03cb85b40/v2/datasets/pfxdev-sql.database.windows.net,SampleDB/query/sql + x-ms-request-url: /apim/sql/53f515b50c3e4925803ec1f0945e799f/v2/datasets/pfxdev-sql.database.windows.net,SampleDB/query/sql x-ms-user-agent: PowerFx/{PowerPlatformConnectorClient.Version} [content-header] Content-Type: application/json; charset=utf-8 - [body] {{""query"":""select name, object_id, parent_object_id, referenced_object_id from sys.foreign_keys; select object_id, name from sys.tables; select constraint_object_id, parent_column_id, parent_object_id, referenced_column_id, referenced_object_id from sys.foreign_key_columns; select name, object_id, column_id from sys.columns""}} + [body] {{""query"":""SELECT fk.name AS FK_Name, \u0027[\u0027 \u002B sp.name \u002B \u0027].[\u0027 \u002B tp.name \u002B \u0027]\u0027 AS Parent_Table, cp.name AS Parent_Column, \u0027[\u0027 \u002B sr.name \u002B \u0027].[\u0027 \u002B tr.name \u002B \u0027]\u0027 AS Referenced_Table, cr.name AS Referenced_Column FROM sys.foreign_keys fk INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id INNER JOIN sys.schemas sp on tp.schema_id = sp.schema_id INNER JOIN sys.schemas sr on tr.schema_id = sr.schema_id INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id WHERE \u0027[\u0027 \u002B sp.name \u002B \u0027].[\u0027 \u002B tp.name \u002B \u0027]\u0027 = \u0027[SalesLT].[Product]\u0027""}} "; - Assert.Equal(expected, testConnector._log.ToString()); } @@ -1494,51 +1456,30 @@ public class SqlRelationship public string ColumnName; public string ReferencedTable; public string ReferencedColumnName; - public long ColumnId; public override string ToString() => $"{RelationshipName}, {ParentTable}, {ColumnName}, {ReferencedTable}, {ReferencedColumnName}"; } - public class Result + internal class RelationshipResult { - public ResultSets ResultSets { get; set; } + public RelationshipResultSets ResultSets { get; set; } } - public class ResultSets + internal class RelationshipResultSets { - public SqlForeignKey[] Table1 { get; set; } - public SqlTable[] Table2 { get; set; } - public SqlForeignKeyColumn[] Table3 { get; set; } - public SqlColumn[] Table4 { get; set; } + public FKRelationship[] Table1 { get; set; } } - public class SqlForeignKey + internal class FKRelationship { - public string name { get; set; } - public long object_id { get; set; } - public long parent_object_id { get; set; } - public long referenced_object_id { get; set; } - } + public string FK_Name { get; set; } - public class SqlTable - { - public long object_id { get; set; } - public string name { get; set; } - } + public string Parent_Table { get; set; } - public class SqlForeignKeyColumn - { - public long constraint_object_id { get; set; } - public long parent_column_id { get; set; } - public long parent_object_id { get; set; } - public long referenced_column_id { get; set; } - public long referenced_object_id { get; set; } - } + public string Parent_Column { get; set; } - public class SqlColumn - { - public string name { get; set; } - public long object_id { get; set; } - public long column_id { get; set; } + public string Referenced_Table { get; set; } + + public string Referenced_Column { get; set; } } #pragma warning restore SA1516 @@ -1616,7 +1557,7 @@ public async Task SQL_ExecuteStoredProc_WithUserAgent() config.AddActionConnector("SQL", apiDoc, new ConsoleLogger(_output)); var engine = new RecalcEngine(config); - + RuntimeConfig rc = new RuntimeConfig(); rc.SetTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time")); rc.AddRuntimeContext(new TestConnectorRuntimeContext("SQL", client, console: _output)); @@ -2083,52 +2024,57 @@ public async Task DVDynamicReturnType() string ft = returnType.FormulaType.ToStringWithDisplayNames(); string expected = - "!['@odata.nextLink'`'Next link':s, value:*[Array:!['@odata.id'`'OData Id':s, _createdby_value`'Created By (Value)':s, '_createdby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (Type)':s, " + - "_createdbyexternalparty_value`'Created By (External Party) (Value)':s, '_createdbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (External Party) (Type)':s, _createdonbehalfby_value`'Created " + - "By (Delegate) (Value)':s, '_createdonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (Delegate) (Type)':s, _defaultpricelevelid_value`'Price List (Value)':s, '_defaultpricelevelid_value@Microsoft.Dynamics.CRM.lookuplogic" + - "alname'`'Price List (Type)':s, _masterid_value`'Master ID (Value)':s, '_masterid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Master ID (Type)':s, _modifiedby_value`'Modified By (Value)':s, '_modifiedby_value@Microsoft.Dynamics.CRM.looku" + - "plogicalname'`'Modified By (Type)':s, _modifiedbyexternalparty_value`'Modified By (External Party) (Value)':s, '_modifiedbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (External " + - "Party) (Type)':s, _modifiedonbehalfby_value`'Modified By (Delegate) (Value)':s, '_modifiedonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (Delegate) (Type)':s, _msa_managingpartnerid_value`'Managing " + - "Partner (Value)':s, '_msa_managingpartnerid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Managing Partner (Type)':s, _msdyn_accountkpiid_value`'KPI (Value)':s, '_msdyn_accountkpiid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'KPI " + - "(Type)':s, _msdyn_salesaccelerationinsightid_value`'Sales Acceleration Insights ID (Value)':s, '_msdyn_salesaccelerationinsightid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Sales Acceleration Insights " + - "ID (Type)':s, _originatingleadid_value`'Originating Lead (Value)':s, '_originatingleadid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Originating Lead (Type)':s, _ownerid_value`'Owner (Value)':s, '_ownerid_value@Microsoft.Dynamics.CRM.lo" + - "okuplogicalname'`'Owner (Type)':s, _owningbusinessunit_value`'Owning Business Unit (Value)':s, '_owningbusinessunit_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning Business Unit (Type)':s, _owningteam_value`'Owning " + - "Team (Value)':s, '_owningteam_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning Team (Type)':s, _owninguser_value`'Owning User (Value)':s, '_owninguser_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning " + - "User (Type)':s, _parentaccountid_value`'Parent Account (Value)':s, '_parentaccountid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Parent Account (Type)':s, _preferredequipmentid_value`'Preferred Facility/Equipment " + - "(Value)':s, '_preferredequipmentid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred Facility/Equipment (Type)':s, _preferredserviceid_value`'Preferred Service (Value)':s, '_preferredserviceid_value@Microsoft.Dynamics.CRM.lookuplogi" + - "calname'`'Preferred Service (Type)':s, _preferredsystemuserid_value`'Preferred User (Value)':s, '_preferredsystemuserid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred User (Type)':s, _primarycontactid_value`'Primary " + - "Contact (Value)':s, '_primarycontactid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Primary Contact (Type)':s, _slaid_value`'SLA (Value)':s, '_slaid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'SLA " + - "(Type)':s, _slainvokedid_value`'Last SLA applied (Value)':s, '_slainvokedid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Last SLA applied (Type)':s, _territoryid_value`'Territory (Value)':s, '_territoryid_value@Microsoft.Dynamics.CRM.loo" + - "kuplogicalname'`'Territory (Type)':s, _transactioncurrencyid_value`'Currency (Value)':s, '_transactioncurrencyid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Currency (Type)':s, accountcategorycode`Category:w, " + - "accountclassificationcode`Classification:w, accountid`Account:s, accountnumber`'Account Number':s, accountratingcode`'Account Rating':w, address1_addressid`'Address 1: ID':s, address1_addresstypecode`'Address " + - "1: Address Type':w, address1_city`'Address 1: City':s, address1_composite`'Address 1':s, address1_country`'Address 1: Country/Region':s, address1_county`'Address 1: County':s, address1_fax`'Address 1: " + - "Fax':s, address1_freighttermscode`'Address 1: Freight Terms':w, address1_latitude`'Address 1: Latitude':w, address1_line1`'Address 1: Street 1':s, address1_line2`'Address 1: Street 2':s, address1_line3`'Address " + - "1: Street 3':s, address1_longitude`'Address 1: Longitude':w, address1_name`'Address 1: Name':s, address1_postalcode`'Address 1: ZIP/Postal Code':s, address1_postofficebox`'Address 1: Post Office Box':s, " + - "address1_primarycontactname`'Address 1: Primary Contact Name':s, address1_shippingmethodcode`'Address 1: Shipping Method':w, address1_stateorprovince`'Address 1: State/Province':s, address1_telephone1`'Address " + - "Phone':s, address1_telephone2`'Address 1: Telephone 2':s, address1_telephone3`'Address 1: Telephone 3':s, address1_upszone`'Address 1: UPS Zone':s, address1_utcoffset`'Address 1: UTC Offset':w, address2_addressid`'Address " + - "2: ID':s, address2_addresstypecode`'Address 2: Address Type':w, address2_city`'Address 2: City':s, address2_composite`'Address 2':s, address2_country`'Address 2: Country/Region':s, address2_county`'Address " + - "2: County':s, address2_fax`'Address 2: Fax':s, address2_freighttermscode`'Address 2: Freight Terms':w, address2_latitude`'Address 2: Latitude':w, address2_line1`'Address 2: Street 1':s, address2_line2`'Address " + - "2: Street 2':s, address2_line3`'Address 2: Street 3':s, address2_longitude`'Address 2: Longitude':w, address2_name`'Address 2: Name':s, address2_postalcode`'Address 2: ZIP/Postal Code':s, address2_postofficebox`'Address " + - "2: Post Office Box':s, address2_primarycontactname`'Address 2: Primary Contact Name':s, address2_shippingmethodcode`'Address 2: Shipping Method':w, address2_stateorprovince`'Address 2: State/Province':s, " + - "address2_telephone1`'Address 2: Telephone 1':s, address2_telephone2`'Address 2: Telephone 2':s, address2_telephone3`'Address 2: Telephone 3':s, address2_upszone`'Address 2: UPS Zone':s, address2_utcoffset`'Address " + - "2: UTC Offset':w, adx_createdbyipaddress`'Created By (IP Address)':s, adx_createdbyusername`'Created By (User Name)':s, adx_modifiedbyipaddress`'Modified By (IP Address)':s, adx_modifiedbyusername`'Modified " + - "By (User Name)':s, aging30`'Aging 30':w, aging30_base`'Aging 30 (Base)':w, aging60`'Aging 60':w, aging60_base`'Aging 60 (Base)':w, aging90`'Aging 90':w, aging90_base`'Aging 90 (Base)':w, businesstypecode`'Business " + - "Type':w, createdon`'Created On':d, creditlimit`'Credit Limit':w, creditlimit_base`'Credit Limit (Base)':w, creditonhold`'Credit Hold':b, customersizecode`'Customer Size':w, customertypecode`'Relationship " + - "Type':w, description`Description:s, donotbulkemail`'Do not allow Bulk Emails':b, donotbulkpostalmail`'Do not allow Bulk Mails':b, donotemail`'Do not allow Emails':b, donotfax`'Do not allow Faxes':b, donotphone`'Do " + - "not allow Phone Calls':b, donotpostalmail`'Do not allow Mails':b, donotsendmm`'Send Marketing Materials':b, emailaddress1`Email:s, emailaddress2`'Email Address 2':s, emailaddress3`'Email Address 3':s, " + - "entityimage`'Default Image':s, entityimageid`'Entity Image Id':s, exchangerate`'Exchange Rate':w, fax`Fax:s, followemail`'Follow Email Activity':b, ftpsiteurl`'FTP Site':s, importsequencenumber`'Import " + - "Sequence Number':w, industrycode`Industry:w, lastonholdtime`'Last On Hold Time':d, lastusedincampaign`'Last Date Included in Campaign':d, marketcap`'Market Capitalization':w, marketcap_base`'Market Capitalization " + - "(Base)':w, marketingonly`'Marketing Only':b, merged`Merged:b, modifiedon`'Modified On':d, msdyn_gdproptout`'GDPR Optout':b, name`'Account Name':s, numberofemployees`'Number of Employees':w, onholdtime`'On " + - "Hold Time (Minutes)':w, opendeals`'Open Deals':w, opendeals_date`'Open Deals (Last Updated On)':d, opendeals_state`'Open Deals (State)':w, openrevenue`'Open Revenue':w, openrevenue_base`'Open Revenue (Base)':w, " + - "openrevenue_date`'Open Revenue (Last Updated On)':d, openrevenue_state`'Open Revenue (State)':w, overriddencreatedon`'Record Created On':d, ownershipcode`Ownership:w, participatesinworkflow`'Participates " + - "in Workflow':b, paymenttermscode`'Payment Terms':w, preferredappointmentdaycode`'Preferred Day':w, preferredappointmenttimecode`'Preferred Time':w, preferredcontactmethodcode`'Preferred Method of Contact':w, " + - "primarysatoriid`'Primary Satori ID':s, primarytwitterid`'Primary Twitter ID':s, processid`Process:s, revenue`'Annual Revenue':w, revenue_base`'Annual Revenue (Base)':w, sharesoutstanding`'Shares Outstanding':w, " + - "shippingmethodcode`'Shipping Method':w, sic`'SIC Code':s, stageid`'(Deprecated) Process Stage':s, statecode`Status:w, statuscode`'Status Reason':w, stockexchange`'Stock Exchange':s, teamsfollowed`TeamsFollowed:w, " + - "telephone1`'Main Phone':s, telephone2`'Other Phone':s, telephone3`'Telephone 3':s, territorycode`'Territory Code':w, tickersymbol`'Ticker Symbol':s, timespentbymeonemailandmeetings`'Time Spent by me':s, " + - "timezoneruleversionnumber`'Time Zone Rule Version Number':w, traversedpath`'(Deprecated) Traversed Path':s, utcconversiontimezonecode`'UTC Conversion Time Zone Code':w, versionnumber`'Version Number':w, " + - "websiteurl`Website:s, yominame`'Yomi Account Name':s]]]"; - - Assert.Equal(expected, ft); + "!['@odata.nextLink'`'Next link':s, value:*[Array:!['@odata.id'`'OData Id':s, _createdby_value`'Created By (Value)':s, '_createdby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created " + + "By (Type)':s, _createdbyexternalparty_value`'Created By (External Party) (Value)':s, '_createdbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (External Party) " + + "(Type)':s, _createdonbehalfby_value`'Created By (Delegate) (Value)':s, '_createdonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (Delegate) (Type)':s, _defaultpricelevelid_value`'" + + "Price List (Value)':s, '_defaultpricelevelid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Price List (Type)':s, _masterid_value`'Master ID (Value)':s, '_masterid_value@Microsoft.Dynamics.CRM.lookup" + + "logicalname'`'Master ID (Type)':s, _modifiedby_value`'Modified By (Value)':s, '_modifiedby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (Type)':s, _modifiedbyexternalparty_value`'Modifi" + + "ed By (External Party) (Value)':s, '_modifiedbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (External Party) (Type)':s, _modifiedonbehalfby_value`'Modified " + + "By (Delegate) (Value)':s, '_modifiedonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (Delegate) (Type)':s, _msa_managingpartnerid_value`'Managing Partner (Value)':s, " + + "'_msa_managingpartnerid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Managing Partner (Type)':s, _msdyn_accountkpiid_value`'KPI (Value)':s, '_msdyn_accountkpiid_value@Microsoft.Dynamics.CRM.lookupl" + + "ogicalname'`'KPI (Type)':s, _msdyn_salesaccelerationinsightid_value`'Sales Acceleration Insights ID (Value)':s, '_msdyn_salesaccelerationinsightid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Sales" + + " Acceleration Insights ID (Type)':s, _originatingleadid_value`'Originating Lead (Value)':s, '_originatingleadid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Originating Lead (Type)':s, " + + "_ownerid_value`'Owner (Value)':s, '_ownerid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owner (Type)':s, _owningbusinessunit_value`'Owning Business Unit (Value)':s, '_owningbusinessunit_value@Micr" + + "osoft.Dynamics.CRM.lookuplogicalname'`'Owning Business Unit (Type)':s, _owningteam_value`'Owning Team (Value)':s, '_owningteam_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning " + + "Team (Type)':s, _owninguser_value`'Owning User (Value)':s, '_owninguser_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning User (Type)':s, _parentaccountid_value`'Parent Account " + + "(Value)':s, '_parentaccountid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Parent Account (Type)':s, _preferredequipmentid_value`'Preferred Facility/Equipment (Value)':s, '_preferredequipmentid_val" + + "ue@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred Facility/Equipment (Type)':s, _preferredserviceid_value`'Preferred Service (Value)':s, '_preferredserviceid_value@Microsoft.Dynamics.CRM.lookuplo" + + "gicalname'`'Preferred Service (Type)':s, _preferredsystemuserid_value`'Preferred User (Value)':s, '_preferredsystemuserid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred " + + "User (Type)':s, _primarycontactid_value`'Primary Contact (Value)':s, '_primarycontactid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Primary Contact (Type)':s, _slaid_value`'SLA " + + "(Value)':s, '_slaid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'SLA (Type)':s, _slainvokedid_value`'Last SLA applied (Value)':s, '_slainvokedid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Las" + + "t SLA applied (Type)':s, _territoryid_value`'Territory (Value)':s, '_territoryid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Territory (Type)':s, _transactioncurrencyid_value`'Currency " + + "(Value)':s, '_transactioncurrencyid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Currency (Type)':s, accountcategorycode`Category:w, accountclassificationcode`Classification:w, " + + "accountid`Account:s, accountnumber`'Account Number':s, accountratingcode`'Account Rating':w, address1_addressid`'Address 1: ID':s, address1_addresstypecode`'Address 1: Address Type':w, " + + "address1_city`'Address 1: City':s, address1_composite`'Address 1':s, address1_country`'Address 1: Country/Region':s, address1_county`'Address 1: County':s, address1_fax`'Address 1: " + + "Fax':s, address1_freighttermscode`'Address 1: Freight Terms':w, address1_latitude`'Address 1: Latitude':w, address1_line1`'Address 1: Street 1':s, address1_line2`'Address 1: Street " + + "2':s, address1_line3`'Address 1: Street 3':s, address1_longitude`'Address 1: Longitude':w, address1_name`'Address 1: Name':s, address1_postalcode`'Address 1: ZIP/Postal Code':s, address1_postofficebox`" + + "'Address 1: Post Office Box':s, address1_primarycontactname`'Address 1: Primary Contact Name':s, address1_shippingmethodcode`'Address 1: Shipping Method':w, address1_stateorprovince`'Address " + + "1: State/Province':s, address1_telephone1`'Address Phone':s, address1_telephone2`'Address 1: Telephone 2':s, address1_telephone3`'Address 1: Telephone 3':s, address1_upszone`'Address " + + "1: UPS Zone':s, address1_utcoffset`'Address 1: UTC Offset':w, address2_addressid`'Address 2: ID':s, address2_addresstypecode`'Address 2: Address Type':w, address2_city`'Address 2: City':s, " + + "address2_composite`'Address 2':s, address2_country`'Address 2: Country/Region':s, address2_county`'Address 2: County':s, address2_fax`'Address 2: Fax':s, address2_freighttermscode`'Address " + + "2: Freight Terms':w, address2_latitude`'Address 2: Latitude':w, address2_line1`'Address 2: Street 1':s, address2_line2`'Address 2: Street 2':s, address2_line3`'Address 2: Street 3':s, " + + "address2_longitude`'Address 2: Longitude':w, address2_name`'Address 2: Name':s, address2_postalcode`'Address 2: ZIP/Postal Code':s, address2_postofficebox`'Address 2: Post Office Box':s, " + + "address2_primarycontactname`'Address 2: Primary Contact Name':s, address2_shippingmethodcode`'Address 2: Shipping Method':w, address2_stateorprovince`'Address 2: State/Province':s, " + + "address2_telephone1`'Address 2: Telephone 1':s, address2_telephone2`'Address 2: Telephone 2':s, address2_telephone3`'Address 2: Telephone 3':s, address2_upszone`'Address 2: UPS Zone':s, " + + "address2_utcoffset`'Address 2: UTC Offset':w, adx_createdbyipaddress`'Created By (IP Address)':s, adx_createdbyusername`'Created By (User Name)':s, adx_modifiedbyipaddress`'Modified " + + "By (IP Address)':s, adx_modifiedbyusername`'Modified By (User Name)':s, aging30`'Aging 30':w, aging30_base`'Aging 30 (Base)':w, aging60`'Aging 60':w, aging60_base`'Aging 60 (Base)':w, " + + "aging90`'Aging 90':w, aging90_base`'Aging 90 (Base)':w, businesstypecode`'Business Type':w, createdon`'Created On':d, creditlimit`'Credit Limit':w, creditlimit_base`'Credit Limit (Base)':w, " + + "creditonhold`'Credit Hold':b, customersizecode`'Customer Size':w, customertypecode`'Relationship Type':w, description`Description:s, donotbulkemail`'Do not allow Bulk Emails':b, donotbulkpostalmail`'Do" + + " not allow Bulk Mails':b, donotemail`'Do not allow Emails':b, donotfax`'Do not allow Faxes':b, donotphone`'Do not allow Phone Calls':b, donotpostalmail`'Do not allow Mails':b, donotsendmm`'Send " + + "Marketing Materials':b, emailaddress1`Email:s, emailaddress2`'Email Address 2':s, emailaddress3`'Email Address 3':s, entityimage`'Default Image':s, entityimageid`'Entity Image Id':s, " + + "exchangerate`'Exchange Rate':w, fax`Fax:s, followemail`'Follow Email Activity':b, ftpsiteurl`'FTP Site':s, importsequencenumber`'Import Sequence Number':w, industrycode`Industry:w, " + + "lastonholdtime`'Last On Hold Time':d, lastusedincampaign`'Last Date Included in Campaign':d, marketcap`'Market Capitalization':w, marketcap_base`'Market Capitalization (Base)':w, marketingonly`'Marketi" + + "ng Only':b, merged`Merged:b, modifiedon`'Modified On':d, msdyn_gdproptout`'GDPR Optout':b, name`'Account Name':s, numberofemployees`'Number of Employees':w, onholdtime`'On Hold Time " + + "(Minutes)':w, opendeals`'Open Deals':w, opendeals_date`'Open Deals (Last Updated On)':d, opendeals_state`'Open Deals (State)':w, openrevenue`'Open Revenue':w, openrevenue_base`'Open " + + "Revenue (Base)':w, openrevenue_date`'Open Revenue (Last Updated On)':d, openrevenue_state`'Open Revenue (State)':w, overriddencreatedon`'Record Created On':d, ownershipcode`Ownership:w, " + + "participatesinworkflow`'Participates in Workflow':b, paymenttermscode`'Payment Terms':w, preferredappointmentdaycode`'Preferred Day':w, preferredappointmenttimecode`'Preferred Time':w, " + + "preferredcontactmethodcode`'Preferred Method of Contact':w, primarysatoriid`'Primary Satori ID':s, primarytwitterid`'Primary Twitter ID':s, processid`Process:s, revenue`'Annual Revenue':w, " + + "revenue_base`'Annual Revenue (Base)':w, sharesoutstanding`'Shares Outstanding':w, shippingmethodcode`'Shipping Method':w, sic`'SIC Code':s, stageid`'(Deprecated) Process Stage':s, statecode`Status:w, " + + "statuscode`'Status Reason':w, stockexchange`'Stock Exchange':s, teamsfollowed`TeamsFollowed:w, telephone1`'Main Phone':s, telephone2`'Other Phone':s, telephone3`'Telephone 3':s, territorycode`'Territor" + + "y Code':w, tickersymbol`'Ticker Symbol':s, timespentbymeonemailandmeetings`'Time Spent by me':s, timezoneruleversionnumber`'Time Zone Rule Version Number':w, traversedpath`'(Deprecated) " + + "Traversed Path':s, utcconversiontimezonecode`'UTC Conversion Time Zone Code':w, versionnumber`'Version Number':w, websiteurl`Website:s, yominame`'Yomi Account Name':s]]]"; + + Assert.Equal(expected, ft); Assert.Equal("address1_addresstypecode", returnType.Fields[0].Fields[0].Fields[7].Name); Assert.Equal("w", returnType.Fields[0].Fields[0].Fields[7].FormulaType.ToStringWithDisplayNames()); Assert.True(returnType.Fields[0].Fields[0].Fields[7].IsEnum); diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs index 4dd691f33b..6145d83298 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformTabularTests.cs @@ -15,6 +15,8 @@ using Xunit; using Xunit.Abstractions; +#pragma warning disable SA1116 + namespace Microsoft.PowerFx.Connectors.Tests { public class PowerPlatformTabularTests @@ -97,40 +99,26 @@ public async Task SQL_CdpTabular_GetTables() CdpTableValue sqlTable = connectorTable.GetTableValue(); Assert.True(sqlTable._tabularService.IsInitialized); Assert.True(sqlTable.IsDelegable); - Assert.Equal("*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type._type.ToString()); + Assert.Equal("r*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type.ToStringWithDisplayNames()); HashSet ads = sqlTable.Type._type.AssociatedDataSources; Assert.NotNull(ads); - - // Tests skipped as ConnectorType.AddDataSource is skipping the creation of AssociatedDataSources -#if false Assert.Single(ads); - TabularDataSource tds = Assert.IsType(ads.First()); - Assert.NotNull(tds); - Assert.NotNull(tds.DataEntityMetadataProvider); + DataSourceInfo dataSourceInfo = Assert.IsType(ads.First()); + Assert.NotNull(dataSourceInfo); - CdpEntityMetadataProvider cemp = Assert.IsType(tds.DataEntityMetadataProvider); - Assert.True(cemp.TryGetEntityMetadata("Customers", out IDataEntityMetadata dem)); + Assert.Equal("Customers", dataSourceInfo.EntityName.Value); + Assert.True(dataSourceInfo.IsDelegatable); + Assert.True(dataSourceInfo.IsPageable); + Assert.True(dataSourceInfo.IsRefreshable); + Assert.True(dataSourceInfo.IsSelectable); + Assert.True(dataSourceInfo.IsWritable); - TabularDataSourceMetadata tdsm = Assert.IsType(dem); - Assert.Equal("pfxdev-sql.database.windows.net,connectortest", tdsm.DatasetName); - Assert.Equal("Customers", tdsm.EntityName); - - Assert.Equal("Customers", tds.EntityName.Value); - Assert.True(tds.IsDelegatable); - Assert.True(tds.IsPageable); - Assert.True(tds.IsRefreshable); - Assert.True(tds.IsSelectable); - Assert.True(tds.IsWritable); - Assert.Equal(DataSourceKind.Connected, tds.Kind); - Assert.Equal("Customers", tds.Name); - Assert.True(tds.RequiresAsync); - Assert.NotNull(tds.ServiceCapabilities); -#endif + Assert.Equal("Customers", dataSourceInfo.Name); + Assert.True(dataSourceInfo.RequiresAsync); - Assert.NotNull(sqlTable._connectorType); - Assert.Null(sqlTable._connectorType.Relationships); + Assert.Null(sqlTable.Relationships); SymbolValues symbolValues = new SymbolValues().Add("Customers", sqlTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -224,13 +212,15 @@ public async Task SQL_CdpTabular_GetTables2() CdpTableValue sqlTable = connectorTable.GetTableValue(); Assert.True(sqlTable._tabularService.IsInitialized); Assert.True(sqlTable.IsDelegable); - Assert.Equal("*[Color:s, DiscontinuedDate:d, ListPrice:w, ModifiedDate:d, Name:s, ProductCategoryID:w, ProductID:w, ProductModelID:w, ProductNumber:s, SellEndDate:d, SellStartDate:d, Size:s, StandardCost:w, ThumbNailPhoto:o, ThumbnailPhotoFileName:s, Weight:w, rowguid:s]", sqlTable.Type._type.ToString()); + + // Note relationships to ProductCategory and ProductModel with ~ notation + Assert.Equal("r*[Color:s, DiscontinuedDate:d, ListPrice:w, ModifiedDate:d, Name:s, ProductCategoryID:~[SalesLT].[ProductCategory]:w, ProductID:w, ProductModelID:~[SalesLT].[ProductModel]:w, ProductNumber:s, " + + "SellEndDate:d, SellStartDate:d, Size:s, StandardCost:w, ThumbNailPhoto:o, ThumbnailPhotoFileName:s, Weight:w, rowguid:s]", sqlTable.Type.ToStringWithDisplayNames()); HashSet ads = sqlTable.Type._type.AssociatedDataSources; Assert.NotNull(ads); - Assert.NotNull(sqlTable._connectorType); - Assert.Null(sqlTable._connectorType.Relationships); // TO BE CHANGED, x-ms-relationships only for now + Assert.Null(sqlTable.Relationships); // TO BE CHANGED, x-ms-relationships only for now SymbolValues symbolValues = new SymbolValues().Add(fxTableName, sqlTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -247,13 +237,13 @@ public async Task SQL_CdpTabular_GetTables2() StringValue address = Assert.IsType(result); Assert.Equal("HL Road Frame - Black, 58", address.Value); - bool b = sqlTable.TabularRecordType.TryGetFieldExternalTableName("ProductModelID", out string externalTableName, out string foreignKey); + bool b = sqlTable.RecordType.TryGetFieldExternalTableName("ProductModelID", out string externalTableName, out string foreignKey); Assert.True(b); - Assert.Equal("ProductModel", externalTableName); // Display Name + Assert.Equal("[SalesLT].[ProductModel]", externalTableName); // Logical Name Assert.Equal("ProductModelID", foreignKey); testConnector.SetResponseFromFiles(@"Responses\SQL GetSchema ProductModel.json", @"Responses\SQL GetRelationships SampleDB.json"); - b = sqlTable.TabularRecordType.TryGetFieldType("ProductModelID", out FormulaType productModelID); + b = sqlTable.RecordType.TryGetFieldType("ProductModelID", out FormulaType productModelID); Assert.True(b); CdpRecordType productModelRecordType = Assert.IsType(productModelID); @@ -262,7 +252,7 @@ public async Task SQL_CdpTabular_GetTables2() // External relationship table name Assert.Equal("[SalesLT].[ProductModel]", productModelRecordType.TableSymbolName); - Assert.Equal("![CatalogDescription:s, ModifiedDate:d, Name:s, ProductModelID:w, rowguid:s]", productModelRecordType.ToStringWithDisplayNames()); // Logical Name + Assert.Equal("r![CatalogDescription:s, ModifiedDate:d, Name:s, ProductModelID:~[SalesLT].[ProductModel]:w, rowguid:s]", productModelRecordType.ToStringWithDisplayNames()); // Logical Name } [Fact] @@ -291,13 +281,15 @@ public async Task SAP_CDP() Assert.True(sapTable.IsInitialized); CdpTableValue sapTableValue = sapTable.GetTableValue(); - Assert.Equal("*[ALL_EMPLOYEES:s, APP_MODE:s, BEGIN_DATE:s, BEGIN_DATE_CHAR:s, COMMAND:s, DESCRIPTION:s, EMP_PERNR:s, END_DATE:s, END_DATE_CHAR:s, EVENT_NAME:s, FLAG:s, GetMessages:*[MESSAGE:s, PERNR:s], HIDE_PEERS:s, LEGEND:s, LEGENDID:s, LEGEND_TEXT:s, PERNR:s, PERNR_MEM_ID:s, TYPE:s]", sapTableValue.Type._type.ToString()); + Assert.Equal( + "r*[ALL_EMPLOYEES:s, APP_MODE:s, BEGIN_DATE:s, BEGIN_DATE_CHAR:s, COMMAND:s, DESCRIPTION:s, EMP_PERNR:s, END_DATE:s, END_DATE_CHAR:s, EVENT_NAME:s, FLAG:s, GetMessages:*[MESSAGE:s, PERNR:s], " + + "HIDE_PEERS:s, LEGEND:s, LEGENDID:s, LEGEND_TEXT:s, PERNR:s, PERNR_MEM_ID:s, TYPE:s]", sapTableValue.Type.ToStringWithDisplayNames()); string expr = "First(TeamCalendarCollection).LEGEND_TEXT"; SymbolValues symbolValues = new SymbolValues().Add("TeamCalendarCollection", sapTableValue); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); - + CheckResult check = engine.Check(expr, options: new ParserOptions() { AllowsSideEffects = true }, symbolTable: symbolValues.SymbolTable); Assert.True(check.IsSuccess); @@ -341,7 +333,7 @@ public async Task SQL_CdpTabular() CdpTableValue sqlTable = tabularService.GetTableValue(); Assert.True(sqlTable._tabularService.IsInitialized); Assert.True(sqlTable.IsDelegable); - Assert.Equal("*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type._type.ToString()); + Assert.Equal("r*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type.ToStringWithDisplayNames()); SymbolValues symbolValues = new SymbolValues().Add("Customers", sqlTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -418,13 +410,13 @@ public async Task SP_CdpTabular_GetTables() Assert.True(spTable._tabularService.IsInitialized); Assert.True(spTable.IsDelegable); - Assert.Equal( - "*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ComplianceAssetId`'Compliance " + - "Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color Tag':s, OData__DisplayName`Sensitivity:s, " + - "OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtension}'`'File name with extension':s, '{FullPath}'`'Full " + - "Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'Comments associated with the content approval of this " + - "list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window " + - "End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames()); + Assert.Equal( + "r*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, " + + "Picture:s], ComplianceAssetId`'Compliance Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color" + + " Tag':s, OData__DisplayName`Sensitivity:s, OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtensi" + + "on}'`'File name with extension':s, '{FullPath}'`'Full Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'" + + "Comments associated with the content approval of this list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, " + + "Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames()); HashSet ads = spTable.Type._type.AssociatedDataSources; Assert.NotNull(ads); @@ -455,13 +447,11 @@ public async Task SP_CdpTabular_GetTables() Assert.True(tds.RequiresAsync); Assert.NotNull(tds.ServiceCapabilities); #endif - - Assert.NotNull(spTable._connectorType); - Assert.NotNull(spTable._connectorType.Relationships); - Assert.Equal(3, spTable._connectorType.Relationships.Count); - Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable._connectorType.Relationships.Select(kvp => kvp.Key))); - Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable._connectorType.Relationships.Select(kvp => kvp.Value.TargetEntity))); - Assert.Equal("Editor#Claims-Claims, Author#Claims-Claims, CheckoutUser#Claims-Claims", string.Join(", ", spTable._connectorType.Relationships.Select(kvp => string.Join("|", kvp.Value.ReferentialConstraints.Select(kvp2 => $"{kvp2.Key}-{kvp2.Value}"))))); + Assert.NotNull(spTable.Relationships); + Assert.Equal(3, spTable.Relationships.Count); + Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable.Relationships.Select(kvp => kvp.Key))); + Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable.Relationships.Select(kvp => kvp.Value.TargetEntity))); + Assert.Equal("Editor#Claims-Claims, Author#Claims-Claims, CheckoutUser#Claims-Claims", string.Join(", ", spTable.Relationships.Select(kvp => string.Join("|", kvp.Value.ReferentialConstraints.Select(kvp2 => $"{kvp2.Key}-{kvp2.Value}"))))); SymbolValues symbolValues = new SymbolValues().Add("Documents", spTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -508,13 +498,13 @@ public async Task SP_CdpTabular() Assert.True(spTable._tabularService.IsInitialized); Assert.True(spTable.IsDelegable); - Assert.Equal( - "*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ComplianceAssetId`'Compliance " + - "Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color Tag':s, OData__DisplayName`Sensitivity:s, " + - "OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtension}'`'File name with extension':s, '{FullPath}'`'Full " + - "Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'Comments associated with the content approval of this " + - "list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window " + - "End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames()); + Assert.Equal( + "r*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, " + + "Picture:s], ComplianceAssetId`'Compliance Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color" + + " Tag':s, OData__DisplayName`Sensitivity:s, OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtensi" + + "on}'`'File name with extension':s, '{FullPath}'`'Full Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'" + + "Comments associated with the content approval of this list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, " + + "Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames()); SymbolValues symbolValues = new SymbolValues().Add("Documents", spTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -660,22 +650,22 @@ public async Task SF_CdpTabular_GetTables() // MasterRecordId`'Master Record ID'[Account]:s // OwnerId`'Owner ID'[User]:s // ParentId`'Parent Account ID'[Account]:s - Assert.Equal( - "![AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " + - "Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID'[User]:s, CreatedDate`'Created Date':d, " + + // Note 2: ~ notation denotes a relationship. Ex: fieldname`displayname:~externaltable:type + Assert.Equal( + "r![AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " + + "Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID'[User]:~User:s, CreatedDate`'Created Date':d, " + "Description`'Account Description':s, Id`'Account ID':s, Industry:l, IsDeleted`Deleted:b, Jigsaw`'Data.com Key':s, JigsawCompanyId`'Jigsaw Company ID':s, LastActivityDate`'Last Activity':D, LastModifiedById`'Last " + - "Modified By ID'[User]:s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID'[Account]:s, Name`'Account " + - "Name':s, NumberOfEmployees`Employees:w, OwnerId`'Owner ID'[User]:s, ParentId`'Parent Account ID'[Account]:s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping City':s, ShippingCountry`'Shipping " + - "Country':s, ShippingGeocodeAccuracy`'Shipping Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping Zip/Postal Code':s, ShippingState`'Shipping " + - "State/Province':s, ShippingStreet`'Shipping Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]", ((CdpRecordType)sfTable.TabularRecordType).ToStringWithDisplayNames()); + "Modified By ID'[User]:~User:s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID'[Account]:~Account:s, " + + "Name`'Account Name':s, NumberOfEmployees`Employees:w, OwnerId`'Owner ID'[User]:~User:s, ParentId`'Parent Account ID'[Account]:~Account:s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping " + + "City':s, ShippingCountry`'Shipping Country':s, ShippingGeocodeAccuracy`'Shipping Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping " + + "Zip/Postal Code':s, ShippingState`'Shipping State/Province':s, ShippingStreet`'Shipping Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]", + ((CdpRecordType)sfTable.RecordType).ToStringWithDisplayNames()); - Assert.Equal("Account", sfTable.TabularRecordType.TableSymbolName); + Assert.Equal("Account", sfTable.RecordType.TableSymbolName); - RecordType rt = sfTable.TabularRecordType; - NamedFormulaType nft = rt.GetFieldTypes().First(); + RecordType rt = sfTable.RecordType; - Assert.Equal("AccountSource", nft.Name); - Assert.Equal("Account Source", nft.DisplayName); + Assert.True(rt.TryGetUnderlyingFieldType("AccountSource", out FormulaType ft)); HashSet ads = sfTable.Type._type.AssociatedDataSources; Assert.NotNull(ads); @@ -707,73 +697,76 @@ public async Task SF_CdpTabular_GetTables() Assert.NotNull(tds.ServiceCapabilities); #endif - Assert.NotNull(sfTable._connectorType); - // SF doesn't use x-ms-releationships extension - Assert.Null(sfTable._connectorType.Relationships); + Assert.Null(sfTable.Relationships); // needs Microsoft.PowerFx.Connectors.CdpExtensions // this call does not make any network call - bool b = sfTable.TabularRecordType.TryGetFieldExternalTableName("OwnerId", out string externalTableName, out string foreignKey); + bool b = sfTable.RecordType.TryGetFieldExternalTableName("OwnerId", out string externalTableName, out string foreignKey); Assert.True(b); Assert.Equal("User", externalTableName); Assert.Null(foreignKey); // Always the case with SalesForce testConnector.SetResponseFromFile(@"Responses\SF GetSchema Users.json"); - b = sfTable.TabularRecordType.TryGetFieldType("OwnerId", out FormulaType ownerIdType); + b = sfTable.RecordType.TryGetFieldType("OwnerId", out FormulaType ownerIdType); Assert.True(b); CdpRecordType userTable = Assert.IsType(ownerIdType); - Assert.False((CdpRecordType)sfTable.TabularRecordType is null); + Assert.False((CdpRecordType)sfTable.RecordType is null); Assert.False(userTable is null); // External relationship table name Assert.Equal("User", userTable.TableSymbolName); - Assert.Equal( - "![AboutMe`'About Me':s, AccountId`'Account ID'[Account]:s, Alias:s, BadgeText`'User Photo badge text overlay':s, BannerPhotoUrl`'Url for banner photo':s, CallCenterId`'Call Center ID':s, City:s, CommunityNickname`Nickname:s, " + - "CompanyName`'Company Name':s, ContactId`'Contact ID'[Contact]:s, Country:s, CreatedById`'Created By ID'[User]:s, CreatedDate`'Created Date':d, DefaultGroupNotificationFrequency`'Default Notification Frequency " + - "when Joining Groups':l, DelegatedApproverId`'Delegated Approver ID':s, Department:s, DigestFrequency`'Chatter Email Highlights Frequency':l, Division:s, Email:s, EmailEncodingKey`'Email Encoding':l, EmailPreferencesAutoBcc`AutoBcc:b, " + - "EmailPreferencesAutoBccStayInTouch`AutoBccStayInTouch:b, EmailPreferencesStayInTouchReminder`StayInTouchReminder:b, EmployeeNumber`'Employee Number':s, Extension:s, Fax:s, FederationIdentifier`'SAML Federation " + - "ID':s, FirstName`'First Name':s, ForecastEnabled`'Allow Forecasting':b, FullPhotoUrl`'Url for full-sized Photo':s, GeocodeAccuracy`'Geocode Accuracy':l, Id`'User ID':s, IsActive`Active:b, IsExtIndicatorVisible`'Show " + - "external indicator':b, IsProfilePhotoActive`'Has Profile Photo':b, LanguageLocaleKey`Language:l, LastLoginDate`'Last Login':d, LastModifiedById`'Last Modified By ID'[User]:s, LastModifiedDate`'Last Modified " + - "Date':d, LastName`'Last Name':s, LastPasswordChangeDate`'Last Password Change or Reset':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, Latitude:w, LocaleSidKey`Locale:l, " + - "Longitude:w, ManagerId`'Manager ID'[User]:s, MediumBannerPhotoUrl`'Url for Android banner photo':s, MediumPhotoUrl`'Url for medium profile photo':s, MiddleName`'Middle Name':s, MobilePhone`Mobile:s, Name`'Full " + - "Name':s, OfflinePdaTrialExpirationDate`'Sales Anywhere Trial Expiration Date':d, OfflineTrialExpirationDate`'Offline Edition Trial Expiration Date':d, OutOfOfficeMessage`'Out of office message':s, Phone:s, " + - "PostalCode`'Zip/Postal Code':s, ProfileId`'Profile ID'[Profile]:s, ReceivesAdminInfoEmails`'Admin Info Emails':b, ReceivesInfoEmails`'Info Emails':b, SenderEmail`'Email Sender Address':s, SenderName`'Email " + - "Sender Name':s, Signature`'Email Signature':s, SmallBannerPhotoUrl`'Url for IOS banner photo':s, SmallPhotoUrl`Photo:s, State`'State/Province':s, StayInTouchNote`'Stay-in-Touch Email Note':s, StayInTouchSignature`'Stay-in-Touch " + - "Email Signature':s, StayInTouchSubject`'Stay-in-Touch Email Subject':s, Street:s, Suffix:s, SystemModstamp`'System Modstamp':d, TimeZoneSidKey`'Time Zone':l, Title:s, UserPermissionsAvantgoUser`'AvantGo " + - "User':b, UserPermissionsCallCenterAutoLogin`'Auto-login To Call Center':b, UserPermissionsInteractionUser`'Flow User':b, UserPermissionsKnowledgeUser`'Knowledge User':b, UserPermissionsLiveAgentUser`'Chat " + - "User':b, UserPermissionsMarketingUser`'Marketing User':b, UserPermissionsMobileUser`'Apex Mobile User':b, UserPermissionsOfflineUser`'Offline User':b, UserPermissionsSFContentUser`'Salesforce CRM Content " + - "User':b, UserPermissionsSupportUser`'Service Cloud User':b, UserPreferencesActivityRemindersPopup`ActivityRemindersPopup:b, UserPreferencesApexPagesDeveloperMode`ApexPagesDeveloperMode:b, UserPreferencesCacheDiagnostics`CacheDiagnostics:b, " + - "UserPreferencesCreateLEXAppsWTShown`CreateLEXAppsWTShown:b, UserPreferencesDisCommentAfterLikeEmail`DisCommentAfterLikeEmail:b, UserPreferencesDisMentionsCommentEmail`DisMentionsCommentEmail:b, UserPreferencesDisProfPostCommentEmail`DisProfP" + - "ostCommentEmail:b, UserPreferencesDisableAllFeedsEmail`DisableAllFeedsEmail:b, UserPreferencesDisableBookmarkEmail`DisableBookmarkEmail:b, UserPreferencesDisableChangeCommentEmail`DisableChangeCommentEmail:b, " + - "UserPreferencesDisableEndorsementEmail`DisableEndorsementEmail:b, UserPreferencesDisableFileShareNotificationsForApi`DisableFileShareNotificationsForApi:b, UserPreferencesDisableFollowersEmail`DisableFollowersEmail:b, " + - "UserPreferencesDisableLaterCommentEmail`DisableLaterCommentEmail:b, UserPreferencesDisableLikeEmail`DisableLikeEmail:b, UserPreferencesDisableMentionsPostEmail`DisableMentionsPostEmail:b, UserPreferencesDisableMessageEmail`DisableMessageEmai" + - "l:b, UserPreferencesDisableProfilePostEmail`DisableProfilePostEmail:b, UserPreferencesDisableSharePostEmail`DisableSharePostEmail:b, UserPreferencesEnableAutoSubForFeeds`EnableAutoSubForFeeds:b, UserPreferencesEventRemindersCheckboxDefault`E" + - "ventRemindersCheckboxDefault:b, UserPreferencesExcludeMailAppAttachments`ExcludeMailAppAttachments:b, UserPreferencesFavoritesShowTopFavorites`FavoritesShowTopFavorites:b, UserPreferencesFavoritesWTShown`FavoritesWTShown:b, " + - "UserPreferencesGlobalNavBarWTShown`GlobalNavBarWTShown:b, UserPreferencesGlobalNavGridMenuWTShown`GlobalNavGridMenuWTShown:b, UserPreferencesHideBiggerPhotoCallout`HideBiggerPhotoCallout:b, UserPreferencesHideCSNDesktopTask`HideCSNDesktopTas" + - "k:b, UserPreferencesHideCSNGetChatterMobileTask`HideCSNGetChatterMobileTask:b, UserPreferencesHideChatterOnboardingSplash`HideChatterOnboardingSplash:b, UserPreferencesHideEndUserOnboardingAssistantModal`HideEndUserOnboardingAssistantModal:b" + - ", UserPreferencesHideLightningMigrationModal`HideLightningMigrationModal:b, UserPreferencesHideS1BrowserUI`HideS1BrowserUI:b, UserPreferencesHideSecondChatterOnboardingSplash`HideSecondChatterOnboardingSplash:b, " + - "UserPreferencesHideSfxWelcomeMat`HideSfxWelcomeMat:b, UserPreferencesLightningExperiencePreferred`LightningExperiencePreferred:b, UserPreferencesPathAssistantCollapsed`PathAssistantCollapsed:b, UserPreferencesPreviewLightning`PreviewLightnin" + - "g:b, UserPreferencesRecordHomeReservedWTShown`RecordHomeReservedWTShown:b, UserPreferencesRecordHomeSectionCollapseWTShown`RecordHomeSectionCollapseWTShown:b, UserPreferencesReminderSoundOff`ReminderSoundOff:b, " + - "UserPreferencesShowCityToExternalUsers`ShowCityToExternalUsers:b, UserPreferencesShowCityToGuestUsers`ShowCityToGuestUsers:b, UserPreferencesShowCountryToExternalUsers`ShowCountryToExternalUsers:b, UserPreferencesShowCountryToGuestUsers`Show" + - "CountryToGuestUsers:b, UserPreferencesShowEmailToExternalUsers`ShowEmailToExternalUsers:b, UserPreferencesShowEmailToGuestUsers`ShowEmailToGuestUsers:b, UserPreferencesShowFaxToExternalUsers`ShowFaxToExternalUsers:b, " + - "UserPreferencesShowFaxToGuestUsers`ShowFaxToGuestUsers:b, UserPreferencesShowManagerToExternalUsers`ShowManagerToExternalUsers:b, UserPreferencesShowManagerToGuestUsers`ShowManagerToGuestUsers:b, UserPreferencesShowMobilePhoneToExternalUsers" + - "`ShowMobilePhoneToExternalUsers:b, UserPreferencesShowMobilePhoneToGuestUsers`ShowMobilePhoneToGuestUsers:b, UserPreferencesShowPostalCodeToExternalUsers`ShowPostalCodeToExternalUsers:b, UserPreferencesShowPostalCodeToGuestUsers`ShowPostalCo" + - "deToGuestUsers:b, UserPreferencesShowProfilePicToGuestUsers`ShowProfilePicToGuestUsers:b, UserPreferencesShowStateToExternalUsers`ShowStateToExternalUsers:b, UserPreferencesShowStateToGuestUsers`ShowStateToGuestUsers:b, " + - "UserPreferencesShowStreetAddressToExternalUsers`ShowStreetAddressToExternalUsers:b, UserPreferencesShowStreetAddressToGuestUsers`ShowStreetAddressToGuestUsers:b, UserPreferencesShowTitleToExternalUsers`ShowTitleToExternalUsers:b, " + - "UserPreferencesShowTitleToGuestUsers`ShowTitleToGuestUsers:b, UserPreferencesShowWorkPhoneToExternalUsers`ShowWorkPhoneToExternalUsers:b, UserPreferencesShowWorkPhoneToGuestUsers`ShowWorkPhoneToGuestUsers:b, " + - "UserPreferencesSortFeedByComment`SortFeedByComment:b, UserPreferencesTaskRemindersCheckboxDefault`TaskRemindersCheckboxDefault:b, UserRoleId`'Role ID'[UserRole]:s, UserType`'User Type':l, Username:s]", userTable.ToStringWithDisplayNames()); + Assert.Equal( + "r![AboutMe`'About Me':s, AccountId`'Account ID'[Account]:~Account:s, Alias:s, BadgeText`'User Photo badge text overlay':s, BannerPhotoUrl`'Url for banner photo':s, CallCenterId`'Call " + + "Center ID':s, City:s, CommunityNickname`Nickname:s, CompanyName`'Company Name':s, ContactId`'Contact ID'[Contact]:~Contact:s, Country:s, CreatedById`'Created By ID'[User]:~User:s, CreatedDate`'Created " + + "Date':d, DefaultGroupNotificationFrequency`'Default Notification Frequency when Joining Groups':l, DelegatedApproverId`'Delegated Approver ID':s, Department:s, DigestFrequency`'Chatter " + + "Email Highlights Frequency':l, Division:s, Email:s, EmailEncodingKey`'Email Encoding':l, EmailPreferencesAutoBcc`AutoBcc:b, EmailPreferencesAutoBccStayInTouch`AutoBccStayInTouch:b, " + + "EmailPreferencesStayInTouchReminder`StayInTouchReminder:b, EmployeeNumber`'Employee Number':s, Extension:s, Fax:s, FederationIdentifier`'SAML Federation ID':s, FirstName`'First Name':s, " + + "ForecastEnabled`'Allow Forecasting':b, FullPhotoUrl`'Url for full-sized Photo':s, GeocodeAccuracy`'Geocode Accuracy':l, Id`'User ID':s, IsActive`Active:b, IsExtIndicatorVisible`'Show " + + "external indicator':b, IsProfilePhotoActive`'Has Profile Photo':b, LanguageLocaleKey`Language:l, LastLoginDate`'Last Login':d, LastModifiedById`'Last Modified By ID'[User]:~User:s, " + + "LastModifiedDate`'Last Modified Date':d, LastName`'Last Name':s, LastPasswordChangeDate`'Last Password Change or Reset':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last " + + "Viewed Date':d, Latitude:w, LocaleSidKey`Locale:l, Longitude:w, ManagerId`'Manager ID'[User]:~User:s, MediumBannerPhotoUrl`'Url for Android banner photo':s, MediumPhotoUrl`'Url for " + + "medium profile photo':s, MiddleName`'Middle Name':s, MobilePhone`Mobile:s, Name`'Full Name':s, OfflinePdaTrialExpirationDate`'Sales Anywhere Trial Expiration Date':d, OfflineTrialExpirationDate`'Offlin" + + "e Edition Trial Expiration Date':d, OutOfOfficeMessage`'Out of office message':s, Phone:s, PostalCode`'Zip/Postal Code':s, ProfileId`'Profile ID'[Profile]:~Profile:s, ReceivesAdminInfoEmails`'Admin " + + "Info Emails':b, ReceivesInfoEmails`'Info Emails':b, SenderEmail`'Email Sender Address':s, SenderName`'Email Sender Name':s, Signature`'Email Signature':s, SmallBannerPhotoUrl`'Url for " + + "IOS banner photo':s, SmallPhotoUrl`Photo:s, State`'State/Province':s, StayInTouchNote`'Stay-in-Touch Email Note':s, StayInTouchSignature`'Stay-in-Touch Email Signature':s, StayInTouchSubject`'Stay-in-T" + + "ouch Email Subject':s, Street:s, Suffix:s, SystemModstamp`'System Modstamp':d, TimeZoneSidKey`'Time Zone':l, Title:s, UserPermissionsAvantgoUser`'AvantGo User':b, UserPermissionsCallCenterAutoLogin`'Au" + + "to-login To Call Center':b, UserPermissionsInteractionUser`'Flow User':b, UserPermissionsKnowledgeUser`'Knowledge User':b, UserPermissionsLiveAgentUser`'Chat User':b, UserPermissionsMarketingUser`'Mark" + + "eting User':b, UserPermissionsMobileUser`'Apex Mobile User':b, UserPermissionsOfflineUser`'Offline User':b, UserPermissionsSFContentUser`'Salesforce CRM Content User':b, UserPermissionsSupportUser`'Ser" + + "vice Cloud User':b, UserPreferencesActivityRemindersPopup`ActivityRemindersPopup:b, UserPreferencesApexPagesDeveloperMode`ApexPagesDeveloperMode:b, UserPreferencesCacheDiagnostics`CacheDiagnostics:b, " + + "UserPreferencesCreateLEXAppsWTShown`CreateLEXAppsWTShown:b, UserPreferencesDisCommentAfterLikeEmail`DisCommentAfterLikeEmail:b, UserPreferencesDisMentionsCommentEmail`DisMentionsCommentEmail:b, " + + "UserPreferencesDisProfPostCommentEmail`DisProfPostCommentEmail:b, UserPreferencesDisableAllFeedsEmail`DisableAllFeedsEmail:b, UserPreferencesDisableBookmarkEmail`DisableBookmarkEmail:b, " + + "UserPreferencesDisableChangeCommentEmail`DisableChangeCommentEmail:b, UserPreferencesDisableEndorsementEmail`DisableEndorsementEmail:b, UserPreferencesDisableFileShareNotificationsForApi`DisableFileSha" + + "reNotificationsForApi:b, UserPreferencesDisableFollowersEmail`DisableFollowersEmail:b, UserPreferencesDisableLaterCommentEmail`DisableLaterCommentEmail:b, UserPreferencesDisableLikeEmail`DisableLikeEma" + + "il:b, UserPreferencesDisableMentionsPostEmail`DisableMentionsPostEmail:b, UserPreferencesDisableMessageEmail`DisableMessageEmail:b, UserPreferencesDisableProfilePostEmail`DisableProfilePostEmail:b, " + + "UserPreferencesDisableSharePostEmail`DisableSharePostEmail:b, UserPreferencesEnableAutoSubForFeeds`EnableAutoSubForFeeds:b, UserPreferencesEventRemindersCheckboxDefault`EventRemindersCheckboxDefault:b," + + " UserPreferencesExcludeMailAppAttachments`ExcludeMailAppAttachments:b, UserPreferencesFavoritesShowTopFavorites`FavoritesShowTopFavorites:b, UserPreferencesFavoritesWTShown`FavoritesWTShown:b, " + + "UserPreferencesGlobalNavBarWTShown`GlobalNavBarWTShown:b, UserPreferencesGlobalNavGridMenuWTShown`GlobalNavGridMenuWTShown:b, UserPreferencesHideBiggerPhotoCallout`HideBiggerPhotoCallout:b, " + + "UserPreferencesHideCSNDesktopTask`HideCSNDesktopTask:b, UserPreferencesHideCSNGetChatterMobileTask`HideCSNGetChatterMobileTask:b, UserPreferencesHideChatterOnboardingSplash`HideChatterOnboardingSplash:" + + "b, UserPreferencesHideEndUserOnboardingAssistantModal`HideEndUserOnboardingAssistantModal:b, UserPreferencesHideLightningMigrationModal`HideLightningMigrationModal:b, UserPreferencesHideS1BrowserUI`Hid" + + "eS1BrowserUI:b, UserPreferencesHideSecondChatterOnboardingSplash`HideSecondChatterOnboardingSplash:b, UserPreferencesHideSfxWelcomeMat`HideSfxWelcomeMat:b, UserPreferencesLightningExperiencePreferred`L" + + "ightningExperiencePreferred:b, UserPreferencesPathAssistantCollapsed`PathAssistantCollapsed:b, UserPreferencesPreviewLightning`PreviewLightning:b, UserPreferencesRecordHomeReservedWTShown`RecordHomeRes" + + "ervedWTShown:b, UserPreferencesRecordHomeSectionCollapseWTShown`RecordHomeSectionCollapseWTShown:b, UserPreferencesReminderSoundOff`ReminderSoundOff:b, UserPreferencesShowCityToExternalUsers`ShowCityTo" + + "ExternalUsers:b, UserPreferencesShowCityToGuestUsers`ShowCityToGuestUsers:b, UserPreferencesShowCountryToExternalUsers`ShowCountryToExternalUsers:b, UserPreferencesShowCountryToGuestUsers`ShowCountryTo" + + "GuestUsers:b, UserPreferencesShowEmailToExternalUsers`ShowEmailToExternalUsers:b, UserPreferencesShowEmailToGuestUsers`ShowEmailToGuestUsers:b, UserPreferencesShowFaxToExternalUsers`ShowFaxToExternalUs" + + "ers:b, UserPreferencesShowFaxToGuestUsers`ShowFaxToGuestUsers:b, UserPreferencesShowManagerToExternalUsers`ShowManagerToExternalUsers:b, UserPreferencesShowManagerToGuestUsers`ShowManagerToGuestUsers:b" + + ", UserPreferencesShowMobilePhoneToExternalUsers`ShowMobilePhoneToExternalUsers:b, UserPreferencesShowMobilePhoneToGuestUsers`ShowMobilePhoneToGuestUsers:b, UserPreferencesShowPostalCodeToExternalUsers`" + + "ShowPostalCodeToExternalUsers:b, UserPreferencesShowPostalCodeToGuestUsers`ShowPostalCodeToGuestUsers:b, UserPreferencesShowProfilePicToGuestUsers`ShowProfilePicToGuestUsers:b, UserPreferencesShowState" + + "ToExternalUsers`ShowStateToExternalUsers:b, UserPreferencesShowStateToGuestUsers`ShowStateToGuestUsers:b, UserPreferencesShowStreetAddressToExternalUsers`ShowStreetAddressToExternalUsers:b, " + + "UserPreferencesShowStreetAddressToGuestUsers`ShowStreetAddressToGuestUsers:b, UserPreferencesShowTitleToExternalUsers`ShowTitleToExternalUsers:b, UserPreferencesShowTitleToGuestUsers`ShowTitleToGuestUs" + + "ers:b, UserPreferencesShowWorkPhoneToExternalUsers`ShowWorkPhoneToExternalUsers:b, UserPreferencesShowWorkPhoneToGuestUsers`ShowWorkPhoneToGuestUsers:b, UserPreferencesSortFeedByComment`SortFeedByComme" + + "nt:b, UserPreferencesTaskRemindersCheckboxDefault`TaskRemindersCheckboxDefault:b, UserRoleId`'Role ID'[UserRole]:~UserRole:s, UserType`'User Type':l, Username:s]", userTable.ToStringWithDisplayNames()); // Missing field - b = sfTable.TabularRecordType.TryGetFieldType("XYZ", out FormulaType xyzType); + b = sfTable.RecordType.TryGetFieldType("XYZ", out FormulaType xyzType); Assert.False(b); Assert.Null(xyzType); // Field with no relationship - b = sfTable.TabularRecordType.TryGetFieldType("BillingCountry", out FormulaType billingCountryType); + b = sfTable.RecordType.TryGetFieldType("BillingCountry", out FormulaType billingCountryType); Assert.True(b); Assert.Equal("s", billingCountryType._type.ToString()); @@ -822,14 +815,14 @@ public async Task SF_CdpTabular() Assert.True(sfTable._tabularService.IsInitialized); Assert.True(sfTable.IsDelegable); - Assert.Equal( - "*[AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " + - "Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID':s, CreatedDate`'Created Date':d, Description`'Account " + - "Description':s, Id`'Account ID':s, Industry:l, IsDeleted`Deleted:b, Jigsaw`'Data.com Key':s, JigsawCompanyId`'Jigsaw Company ID':s, LastActivityDate`'Last Activity':D, LastModifiedById`'Last Modified By " + - "ID':s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID':s, Name`'Account Name':s, NumberOfEmployees`Employees:w, " + - "OwnerId`'Owner ID':s, ParentId`'Parent Account ID':s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping City':s, ShippingCountry`'Shipping Country':s, ShippingGeocodeAccuracy`'Shipping " + - "Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping Zip/Postal Code':s, ShippingState`'Shipping State/Province':s, ShippingStreet`'Shipping " + - "Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]", sfTable.Type.ToStringWithDisplayNames()); + Assert.Equal( + "r*[AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " + + "Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID':~User:s, CreatedDate`'Created Date':d, " + + "Description`'Account Description':s, Id`'Account ID':s, Industry:l, IsDeleted`Deleted:b, Jigsaw`'Data.com Key':s, JigsawCompanyId`'Jigsaw Company ID':s, LastActivityDate`'Last Activity':D, LastModifiedById`'Last " + + "Modified By ID':~User:s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID':~Account:s, Name`'Account " + + "Name':s, NumberOfEmployees`Employees:w, OwnerId`'Owner ID':~User:s, ParentId`'Parent Account ID':~Account:s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping City':s, ShippingCountry`'Shipping " + + "Country':s, ShippingGeocodeAccuracy`'Shipping Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping Zip/Postal Code':s, ShippingState`'Shipping " + + "State/Province':s, ShippingStreet`'Shipping Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]", sfTable.Type.ToStringWithDisplayNames()); SymbolValues symbolValues = new SymbolValues().Add("Accounts", sfTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -897,9 +890,9 @@ public async Task ZD_CdpTabular_GetTables() Assert.True(zdTable._tabularService.IsInitialized); Assert.True(zdTable.IsDelegable); - Assert.Equal( - "![active:b, alias:s, created_at:d, custom_role_id:w, details:s, email:s, external_id:s, id:w, last_login_at:d, locale:s, locale_id:w, moderator:b, name:s, notes:s, only_private_comments:b, organization_id:w, " + - "phone:s, photo:s, restricted_agent:b, role:s, shared:b, shared_agent:b, signature:s, suspended:b, tags:s, ticket_restriction:s, time_zone:s, updated_at:d, url:s, user_fields:s, verified:b]", ((CdpRecordType)zdTable.TabularRecordType).ToStringWithDisplayNames()); + Assert.Equal( + "r![active:b, alias:s, created_at:d, custom_role_id:w, details:s, email:s, external_id:s, id:w, last_login_at:d, locale:s, locale_id:w, moderator:b, name:s, notes:s, only_private_comments:b, organization_id:w, " + + "phone:s, photo:s, restricted_agent:b, role:s, shared:b, shared_agent:b, signature:s, suspended:b, tags:s, ticket_restriction:s, time_zone:s, updated_at:d, url:s, user_fields:s, verified:b]", ((CdpRecordType)zdTable.RecordType).ToStringWithDisplayNames()); SymbolValues symbolValues = new SymbolValues().Add("Users", zdTable); RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); @@ -916,6 +909,78 @@ public async Task ZD_CdpTabular_GetTables() StringValue userName = Assert.IsType(result); Assert.Equal("Ram Sitwat", userName.Value); } + + [Fact] + public async Task ZD_CdpTabular_GetTables2() + { + using var testConnector = new LoggingTestServer(null /* no swagger */, _output); + var config = new PowerFxConfig(Features.PowerFxV1); + var engine = new RecalcEngine(config); + + ConsoleLogger logger = new ConsoleLogger(_output); + using var httpClient = new HttpClient(testConnector); + string connectionId = "ca06d34f4b684e38b7cf4c0f517a7e99"; + string uriPrefix = $"/apim/zendesk/{connectionId}"; + string jwt = "eyJ0eXA..."; + using var client = new PowerPlatformConnectorClient("4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f.08.common.tip1.azure-apihub.net", "4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" }; + + testConnector.SetResponseFromFile(@"Responses\ZD GetDatasetsMetadata.json"); + DatasetMetadata dm = await CdpDataSource.GetDatasetsMetadataAsync(client, uriPrefix, CancellationToken.None, logger); + + Assert.NotNull(dm); + Assert.Null(dm.Blob); + Assert.Null(dm.DatasetFormat); + Assert.Null(dm.Parameters); + + Assert.NotNull(dm.Tabular); + Assert.Equal("dataset", dm.Tabular.DisplayName); + Assert.Equal("singleton", dm.Tabular.Source); + Assert.Equal("table", dm.Tabular.TableDisplayName); + Assert.Equal("tables", dm.Tabular.TablePluralName); + Assert.Equal("double", dm.Tabular.UrlEncoding); + + CdpDataSource cds = new CdpDataSource("default"); + + // only one network call as we already read metadata + testConnector.SetResponseFromFiles(@"Responses\ZD GetDatasetsMetadata.json", @"Responses\ZD GetTables.json"); + IEnumerable tables = await cds.GetTablesAsync(client, uriPrefix, CancellationToken.None, logger); + + Assert.NotNull(tables); + Assert.Equal(18, tables.Count()); + + CdpTable connectorTable = tables.First(t => t.DisplayName == "Tickets"); + Assert.Equal("tickets", connectorTable.TableName); + Assert.False(connectorTable.IsInitialized); + + testConnector.SetResponseFromFile(@"Responses\ZD Tickets GetSchema.json"); + await connectorTable.InitAsync(client, uriPrefix, CancellationToken.None, logger); + Assert.True(connectorTable.IsInitialized); + + CdpTableValue zdTable = connectorTable.GetTableValue(); + Assert.True(zdTable._tabularService.IsInitialized); + Assert.True(zdTable.IsDelegable); + + Assert.Equal( + "r![assignee_id:w, brand_id:w, collaborator_ids:s, created_at:d, custom_fields:s, description:s, due_at:d, external_id:s, followup_ids:s, forum_topic_id:w, group_id:w, has_incidents:b, " + + "id:w, organization_id:w, priority:l, problem_id:w, raw_subject:s, recipient:s, requester_id:w, satisfaction_rating:s, sharing_agreement_ids:s, status:s, subject:s, submitter_id:w, " + + "tags:s, ticket_form_id:w, type:s, updated_at:d, url:s, via:s]", ((CdpRecordType)zdTable.RecordType).ToStringWithDisplayNames()); + + SymbolValues symbolValues = new SymbolValues().Add("Tickets", zdTable); + RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService(logger); + + // Expression with tabular connector + string expr = @"First(Tickets).priority"; + CheckResult check = engine.Check(expr, options: new ParserOptions() { AllowsSideEffects = true }, symbolTable: symbolValues.SymbolTable); + Assert.True(check.IsSuccess); + + // Use tabular connector. Internally we'll call CdpTableValue.GetRowsInternal to get the data + testConnector.SetResponseFromFile(@"Responses\ZD Tickets GetRows.json"); + FormulaValue result = await check.GetEvaluator().EvalAsync(CancellationToken.None, rc); + + OptionSetValue priority = Assert.IsType(result); + Assert.Equal("normal", priority.Option); + Assert.Equal("normal", priority.DisplayName); + } } public static class Exts2 diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Compatibility GetSchema.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Compatibility GetSchema.json index 80244cb3b5..315708bdae 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Compatibility GetSchema.json +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/Compatibility GetSchema.json @@ -27,7 +27,7 @@ "x-ms-visibility": "important" }, "priority": { - "title": "priority", + "title": "Priority", "type": "string", "enum": [ "low", @@ -37,7 +37,7 @@ ] }, "priority2": { - "title": "priority", + "title": "Priority 2", "type": "string", "format": "enum", "enum": [ diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetRelationships SampleDB.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetRelationships SampleDB.json index a839f27d90..dd16b99aca 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetRelationships SampleDB.json +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/SQL GetRelationships SampleDB.json @@ -2,7313 +2,18 @@ "ResultSets": { "Table1": [ { - "name": "FK_CustomerAddress_Address_AddressID", - "object_id": 626101271, - "parent_object_id": 1925581898, - "referenced_object_id": 1893581784 - }, - { - "name": "FK_CustomerAddress_Customer_CustomerID", - "object_id": 642101328, - "parent_object_id": 1925581898, - "referenced_object_id": 1573580644 - }, - { - "name": "FK_Product_ProductCategory_ProductCategoryID", - "object_id": 658101385, - "parent_object_id": 1701581100, - "referenced_object_id": 1781581385 - }, - { - "name": "FK_Product_ProductModel_ProductModelID", - "object_id": 674101442, - "parent_object_id": 1701581100, - "referenced_object_id": 1621580815 - }, - { - "name": "FK_ProductCategory_ProductCategory_ParentProductCategoryID_ProductCategoryID", - "object_id": 690101499, - "parent_object_id": 1781581385, - "referenced_object_id": 1781581385 - }, - { - "name": "FK_ProductModelProductDescription_ProductDescription_ProductDescriptionID", - "object_id": 706101556, - "parent_object_id": 1733581214, - "referenced_object_id": 1669580986 - }, - { - "name": "FK_ProductModelProductDescription_ProductModel_ProductModelID", - "object_id": 722101613, - "parent_object_id": 1733581214, - "referenced_object_id": 1621580815 - }, - { - "name": "FK_SalesOrderDetail_Product_ProductID", - "object_id": 738101670, - "parent_object_id": 1957582012, - "referenced_object_id": 1701581100 - }, - { - "name": "FK_SalesOrderDetail_SalesOrderHeader_SalesOrderID", - "object_id": 754101727, - "parent_object_id": 1957582012, - "referenced_object_id": 1989582126 - }, - { - "name": "FK_SalesOrderHeader_Address_BillTo_AddressID", - "object_id": 770101784, - "parent_object_id": 1989582126, - "referenced_object_id": 1893581784 - }, - { - "name": "FK_SalesOrderHeader_Address_ShipTo_AddressID", - "object_id": 786101841, - "parent_object_id": 1989582126, - "referenced_object_id": 1893581784 - }, - { - "name": "FK_SalesOrderHeader_Customer_CustomerID", - "object_id": 802101898, - "parent_object_id": 1989582126, - "referenced_object_id": 1573580644 - } - ], - "Table2": [ - { - "object_id": 1074102867, - "name": "sysdiagrams" - }, - { - "object_id": 1573580644, - "name": "Customer" - }, - { - "object_id": 1621580815, - "name": "ProductModel" - }, - { - "object_id": 1669580986, - "name": "ProductDescription" - }, - { - "object_id": 1701581100, - "name": "Product" - }, - { - "object_id": 1733581214, - "name": "ProductModelProductDescription" - }, - { - "object_id": 1781581385, - "name": "ProductCategory" - }, - { - "object_id": 1829581556, - "name": "BuildVersion" - }, - { - "object_id": 1861581670, - "name": "ErrorLog" - }, - { - "object_id": 1893581784, - "name": "Address" - }, - { - "object_id": 1925581898, - "name": "CustomerAddress" - }, - { - "object_id": 1957582012, - "name": "SalesOrderDetail" - }, - { - "object_id": 1989582126, - "name": "SalesOrderHeader" - } - ], - "Table3": [ - { - "constraint_object_id": 642101328, - "parent_column_id": 1, - "parent_object_id": 1925581898, - "referenced_column_id": 1, - "referenced_object_id": 1573580644 - }, - { - "constraint_object_id": 802101898, - "parent_column_id": 11, - "parent_object_id": 1989582126, - "referenced_column_id": 1, - "referenced_object_id": 1573580644 - }, - { - "constraint_object_id": 674101442, - "parent_column_id": 10, - "parent_object_id": 1701581100, - "referenced_column_id": 1, - "referenced_object_id": 1621580815 - }, - { - "constraint_object_id": 722101613, - "parent_column_id": 1, - "parent_object_id": 1733581214, - "referenced_column_id": 1, - "referenced_object_id": 1621580815 - }, - { - "constraint_object_id": 706101556, - "parent_column_id": 2, - "parent_object_id": 1733581214, - "referenced_column_id": 1, - "referenced_object_id": 1669580986 - }, - { - "constraint_object_id": 738101670, - "parent_column_id": 4, - "parent_object_id": 1957582012, - "referenced_column_id": 1, - "referenced_object_id": 1701581100 - }, - { - "constraint_object_id": 658101385, - "parent_column_id": 9, - "parent_object_id": 1701581100, - "referenced_column_id": 1, - "referenced_object_id": 1781581385 - }, - { - "constraint_object_id": 690101499, - "parent_column_id": 2, - "parent_object_id": 1781581385, - "referenced_column_id": 1, - "referenced_object_id": 1781581385 - }, - { - "constraint_object_id": 626101271, - "parent_column_id": 2, - "parent_object_id": 1925581898, - "referenced_column_id": 1, - "referenced_object_id": 1893581784 - }, - { - "constraint_object_id": 770101784, - "parent_column_id": 13, - "parent_object_id": 1989582126, - "referenced_column_id": 1, - "referenced_object_id": 1893581784 - }, - { - "constraint_object_id": 786101841, - "parent_column_id": 12, - "parent_object_id": 1989582126, - "referenced_column_id": 1, - "referenced_object_id": 1893581784 - }, - { - "constraint_object_id": 754101727, - "parent_column_id": 1, - "parent_object_id": 1957582012, - "referenced_column_id": 1, - "referenced_object_id": 1989582126 - } - ], - "Table4": [ - { - "name": "rsid", - "object_id": 3, - "column_id": 1 - }, - { - "name": "rscolid", - "object_id": 3, - "column_id": 2 - }, - { - "name": "hbcolid", - "object_id": 3, - "column_id": 3 - }, - { - "name": "rcmodified", - "object_id": 3, - "column_id": 4 - }, - { - "name": "ti", - "object_id": 3, - "column_id": 5 - }, - { - "name": "cid", - "object_id": 3, - "column_id": 6 - }, - { - "name": "ordkey", - "object_id": 3, - "column_id": 7 - }, - { - "name": "maxinrowlen", - "object_id": 3, - "column_id": 8 - }, - { - "name": "status", - "object_id": 3, - "column_id": 9 - }, - { - "name": "offset", - "object_id": 3, - "column_id": 10 - }, - { - "name": "nullbit", - "object_id": 3, - "column_id": 11 - }, - { - "name": "bitpos", - "object_id": 3, - "column_id": 12 - }, - { - "name": "colguid", - "object_id": 3, - "column_id": 13 - }, - { - "name": "ordlock", - "object_id": 3, - "column_id": 14 - }, - { - "name": "rowsetid", - "object_id": 5, - "column_id": 1 - }, - { - "name": "ownertype", - "object_id": 5, - "column_id": 2 - }, - { - "name": "idmajor", - "object_id": 5, - "column_id": 3 - }, - { - "name": "idminor", - "object_id": 5, - "column_id": 4 - }, - { - "name": "numpart", - "object_id": 5, - "column_id": 5 - }, - { - "name": "status", - "object_id": 5, - "column_id": 6 - }, - { - "name": "fgidfs", - "object_id": 5, - "column_id": 7 - }, - { - "name": "rcrows", - "object_id": 5, - "column_id": 8 - }, - { - "name": "cmprlevel", - "object_id": 5, - "column_id": 9 - }, - { - "name": "fillfact", - "object_id": 5, - "column_id": 10 - }, - { - "name": "maxnullbit", - "object_id": 5, - "column_id": 11 - }, - { - "name": "maxleaf", - "object_id": 5, - "column_id": 12 - }, - { - "name": "maxint", - "object_id": 5, - "column_id": 13 - }, - { - "name": "minleaf", - "object_id": 5, - "column_id": 14 - }, - { - "name": "minint", - "object_id": 5, - "column_id": 15 - }, - { - "name": "rsguid", - "object_id": 5, - "column_id": 16 - }, - { - "name": "lockres", - "object_id": 5, - "column_id": 17 - }, - { - "name": "scope_id", - "object_id": 5, - "column_id": 18 - }, - { - "name": "id", - "object_id": 6, - "column_id": 1 - }, - { - "name": "subid", - "object_id": 6, - "column_id": 2 - }, - { - "name": "partid", - "object_id": 6, - "column_id": 3 - }, - { - "name": "version", - "object_id": 6, - "column_id": 4 - }, - { - "name": "segid", - "object_id": 6, - "column_id": 5 - }, - { - "name": "cloneid", - "object_id": 6, - "column_id": 6 - }, - { - "name": "rowsetid", - "object_id": 6, - "column_id": 7 - }, - { - "name": "dbfragid", - "object_id": 6, - "column_id": 8 - }, - { - "name": "status", - "object_id": 6, - "column_id": 9 - }, - { - "name": "auid", - "object_id": 7, - "column_id": 1 - }, - { - "name": "type", - "object_id": 7, - "column_id": 2 - }, - { - "name": "ownerid", - "object_id": 7, - "column_id": 3 - }, - { - "name": "status", - "object_id": 7, - "column_id": 4 - }, - { - "name": "fgid", - "object_id": 7, - "column_id": 5 - }, - { - "name": "pgfirst", - "object_id": 7, - "column_id": 6 - }, - { - "name": "pgroot", - "object_id": 7, - "column_id": 7 - }, - { - "name": "pgfirstiam", - "object_id": 7, - "column_id": 8 - }, - { - "name": "pcused", - "object_id": 7, - "column_id": 9 - }, - { - "name": "pcdata", - "object_id": 7, - "column_id": 10 - }, - { - "name": "pcreserved", - "object_id": 7, - "column_id": 11 - }, - { - "name": "status", - "object_id": 8, - "column_id": 1 - }, - { - "name": "fileid", - "object_id": 8, - "column_id": 2 - }, - { - "name": "name", - "object_id": 8, - "column_id": 3 - }, - { - "name": "filename", - "object_id": 8, - "column_id": 4 - }, - { - "name": "valclass", - "object_id": 9, - "column_id": 1 - }, - { - "name": "id", - "object_id": 9, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 9, - "column_id": 3 - }, - { - "name": "valnum", - "object_id": 9, - "column_id": 4 - }, - { - "name": "value", - "object_id": 9, - "column_id": 5 - }, - { - "name": "imageval", - "object_id": 9, - "column_id": 6 - }, - { - "name": "priority_id", - "object_id": 17, - "column_id": 1 - }, - { - "name": "name", - "object_id": 17, - "column_id": 2 - }, - { - "name": "service_contract_id", - "object_id": 17, - "column_id": 3 - }, - { - "name": "local_service_id", - "object_id": 17, - "column_id": 4 - }, - { - "name": "remote_service_name", - "object_id": 17, - "column_id": 5 - }, - { - "name": "priority", - "object_id": 17, - "column_id": 6 - }, - { - "name": "dbid", - "object_id": 18, - "column_id": 1 - }, - { - "name": "fragid", - "object_id": 18, - "column_id": 2 - }, - { - "name": "name", - "object_id": 18, - "column_id": 3 - }, - { - "name": "brickid", - "object_id": 18, - "column_id": 4 - }, - { - "name": "pruid", - "object_id": 18, - "column_id": 5 - }, - { - "name": "status", - "object_id": 18, - "column_id": 6 - }, - { - "name": "fgid", - "object_id": 19, - "column_id": 1 - }, - { - "name": "fgfragid", - "object_id": 19, - "column_id": 2 - }, - { - "name": "dbfragid", - "object_id": 19, - "column_id": 3 - }, - { - "name": "phfgid", - "object_id": 19, - "column_id": 4 - }, - { - "name": "status", - "object_id": 19, - "column_id": 5 - }, - { - "name": "dbfragid", - "object_id": 20, - "column_id": 1 - }, - { - "name": "fileid", - "object_id": 20, - "column_id": 2 - }, - { - "name": "fileguid", - "object_id": 20, - "column_id": 3 - }, - { - "name": "pname", - "object_id": 20, - "column_id": 4 - }, - { - "name": "brickid", - "object_id": 21, - "column_id": 1 - }, - { - "name": "dbid", - "object_id": 21, - "column_id": 2 - }, - { - "name": "pruid", - "object_id": 21, - "column_id": 3 - }, - { - "name": "fragid", - "object_id": 21, - "column_id": 4 - }, - { - "name": "status", - "object_id": 21, - "column_id": 5 - }, - { - "name": "brickid", - "object_id": 22, - "column_id": 1 - }, - { - "name": "dbid", - "object_id": 22, - "column_id": 2 - }, - { - "name": "pruid", - "object_id": 22, - "column_id": 3 - }, - { - "name": "fileid", - "object_id": 22, - "column_id": 4 - }, - { - "name": "grpid", - "object_id": 22, - "column_id": 5 - }, - { - "name": "status", - "object_id": 22, - "column_id": 6 - }, - { - "name": "filetype", - "object_id": 22, - "column_id": 7 - }, - { - "name": "filestate", - "object_id": 22, - "column_id": 8 - }, - { - "name": "size", - "object_id": 22, - "column_id": 9 - }, - { - "name": "maxsize", - "object_id": 22, - "column_id": 10 - }, - { - "name": "growth", - "object_id": 22, - "column_id": 11 - }, - { - "name": "lname", - "object_id": 22, - "column_id": 12 - }, - { - "name": "pname", - "object_id": 22, - "column_id": 13 - }, - { - "name": "createlsn", - "object_id": 22, - "column_id": 14 - }, - { - "name": "droplsn", - "object_id": 22, - "column_id": 15 - }, - { - "name": "fileguid", - "object_id": 22, - "column_id": 16 - }, - { - "name": "internalstatus", - "object_id": 22, - "column_id": 17 - }, - { - "name": "readonlylsn", - "object_id": 22, - "column_id": 18 - }, - { - "name": "readwritelsn", - "object_id": 22, - "column_id": 19 - }, - { - "name": "readonlybaselsn", - "object_id": 22, - "column_id": 20 - }, - { - "name": "firstupdatelsn", - "object_id": 22, - "column_id": 21 - }, - { - "name": "lastupdatelsn", - "object_id": 22, - "column_id": 22 - }, - { - "name": "backuplsn", - "object_id": 22, - "column_id": 23 - }, - { - "name": "diffbaselsn", - "object_id": 22, - "column_id": 24 - }, - { - "name": "diffbaseguid", - "object_id": 22, - "column_id": 25 - }, - { - "name": "diffbasetime", - "object_id": 22, - "column_id": 26 - }, - { - "name": "diffbaseseclsn", - "object_id": 22, - "column_id": 27 - }, - { - "name": "redostartlsn", - "object_id": 22, - "column_id": 28 - }, - { - "name": "redotargetlsn", - "object_id": 22, - "column_id": 29 - }, - { - "name": "forkguid", - "object_id": 22, - "column_id": 30 - }, - { - "name": "forklsn", - "object_id": 22, - "column_id": 31 - }, - { - "name": "forkvc", - "object_id": 22, - "column_id": 32 - }, - { - "name": "redostartforkguid", - "object_id": 22, - "column_id": 33 - }, - { - "name": "dbfragid", - "object_id": 23, - "column_id": 1 - }, - { - "name": "phfgid", - "object_id": 23, - "column_id": 2 - }, - { - "name": "fgid", - "object_id": 23, - "column_id": 3 - }, - { - "name": "type", - "object_id": 23, - "column_id": 4 - }, - { - "name": "fgguid", - "object_id": 23, - "column_id": 5 - }, - { - "name": "lgfgid", - "object_id": 23, - "column_id": 6 - }, - { - "name": "status", - "object_id": 23, - "column_id": 7 - }, - { - "name": "name", - "object_id": 23, - "column_id": 8 - }, - { - "name": "dbfragid", - "object_id": 24, - "column_id": 1 - }, - { - "name": "fileid", - "object_id": 24, - "column_id": 2 - }, - { - "name": "grpid", - "object_id": 24, - "column_id": 3 - }, - { - "name": "status", - "object_id": 24, - "column_id": 4 - }, - { - "name": "filetype", - "object_id": 24, - "column_id": 5 - }, - { - "name": "filestate", - "object_id": 24, - "column_id": 6 - }, - { - "name": "size", - "object_id": 24, - "column_id": 7 - }, - { - "name": "maxsize", - "object_id": 24, - "column_id": 8 - }, - { - "name": "growth", - "object_id": 24, - "column_id": 9 - }, - { - "name": "lname", - "object_id": 24, - "column_id": 10 - }, - { - "name": "pname", - "object_id": 24, - "column_id": 11 - }, - { - "name": "createlsn", - "object_id": 24, - "column_id": 12 - }, - { - "name": "droplsn", - "object_id": 24, - "column_id": 13 - }, - { - "name": "fileguid", - "object_id": 24, - "column_id": 14 - }, - { - "name": "internalstatus", - "object_id": 24, - "column_id": 15 - }, - { - "name": "readonlylsn", - "object_id": 24, - "column_id": 16 - }, - { - "name": "readwritelsn", - "object_id": 24, - "column_id": 17 - }, - { - "name": "readonlybaselsn", - "object_id": 24, - "column_id": 18 - }, - { - "name": "firstupdatelsn", - "object_id": 24, - "column_id": 19 - }, - { - "name": "lastupdatelsn", - "object_id": 24, - "column_id": 20 - }, - { - "name": "backuplsn", - "object_id": 24, - "column_id": 21 - }, - { - "name": "diffbaselsn", - "object_id": 24, - "column_id": 22 - }, - { - "name": "diffbaseguid", - "object_id": 24, - "column_id": 23 - }, - { - "name": "diffbasetime", - "object_id": 24, - "column_id": 24 - }, - { - "name": "diffbaseseclsn", - "object_id": 24, - "column_id": 25 - }, - { - "name": "redostartlsn", - "object_id": 24, - "column_id": 26 - }, - { - "name": "redotargetlsn", - "object_id": 24, - "column_id": 27 - }, - { - "name": "forkguid", - "object_id": 24, - "column_id": 28 - }, - { - "name": "forklsn", - "object_id": 24, - "column_id": 29 - }, - { - "name": "forkvc", - "object_id": 24, - "column_id": 30 - }, - { - "name": "redostartforkguid", - "object_id": 24, - "column_id": 31 - }, - { - "name": "id", - "object_id": 25, - "column_id": 1 - }, - { - "name": "indid", - "object_id": 25, - "column_id": 2 - }, - { - "name": "status", - "object_id": 25, - "column_id": 3 - }, - { - "name": "crtype", - "object_id": 25, - "column_id": 4 - }, - { - "name": "crstart", - "object_id": 25, - "column_id": 5 - }, - { - "name": "crend", - "object_id": 25, - "column_id": 6 - }, - { - "name": "crrows", - "object_id": 25, - "column_id": 7 - }, - { - "name": "crerrors", - "object_id": 25, - "column_id": 8 - }, - { - "name": "crschver", - "object_id": 25, - "column_id": 9 - }, - { - "name": "crtsnext", - "object_id": 25, - "column_id": 10 - }, - { - "name": "sensitivity", - "object_id": 25, - "column_id": 11 - }, - { - "name": "bXVTDocidUseBaseT", - "object_id": 25, - "column_id": 12 - }, - { - "name": "batchsize", - "object_id": 25, - "column_id": 13 - }, - { - "name": "nextdocid", - "object_id": 25, - "column_id": 14 - }, - { - "name": "fgid", - "object_id": 25, - "column_id": 15 - }, - { - "name": "id", - "object_id": 27, - "column_id": 1 - }, - { - "name": "name", - "object_id": 27, - "column_id": 2 - }, - { - "name": "type", - "object_id": 27, - "column_id": 3 - }, - { - "name": "sid", - "object_id": 27, - "column_id": 4 - }, - { - "name": "password", - "object_id": 27, - "column_id": 5 - }, - { - "name": "dfltsch", - "object_id": 27, - "column_id": 6 - }, - { - "name": "status", - "object_id": 27, - "column_id": 7 - }, - { - "name": "created", - "object_id": 27, - "column_id": 8 - }, - { - "name": "modified", - "object_id": 27, - "column_id": 9 - }, - { - "name": "deflanguage", - "object_id": 27, - "column_id": 10 - }, - { - "name": "tenantid", - "object_id": 27, - "column_id": 11 - }, - { - "name": "onpremsid", - "object_id": 27, - "column_id": 12 - }, - { - "name": "externaloid", - "object_id": 27, - "column_id": 13 - }, - { - "name": "id", - "object_id": 28, - "column_id": 1 - }, - { - "name": "name", - "object_id": 28, - "column_id": 2 - }, - { - "name": "sid", - "object_id": 28, - "column_id": 3 - }, - { - "name": "status", - "object_id": 28, - "column_id": 4 - }, - { - "name": "status2", - "object_id": 28, - "column_id": 5 - }, - { - "name": "category", - "object_id": 28, - "column_id": 6 - }, - { - "name": "crdate", - "object_id": 28, - "column_id": 7 - }, - { - "name": "modified", - "object_id": 28, - "column_id": 8 - }, - { - "name": "svcbrkrguid", - "object_id": 28, - "column_id": 9 - }, - { - "name": "scope", - "object_id": 28, - "column_id": 10 - }, - { - "name": "cmptlevel", - "object_id": 28, - "column_id": 11 - }, - { - "name": "class", - "object_id": 29, - "column_id": 1 - }, - { - "name": "id", - "object_id": 29, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 29, - "column_id": 3 - }, - { - "name": "grantee", - "object_id": 29, - "column_id": 4 - }, - { - "name": "grantor", - "object_id": 29, - "column_id": 5 - }, - { - "name": "type", - "object_id": 29, - "column_id": 6 - }, - { - "name": "state", - "object_id": 29, - "column_id": 7 - }, - { - "name": "id", - "object_id": 34, - "column_id": 1 - }, - { - "name": "name", - "object_id": 34, - "column_id": 2 - }, - { - "name": "nsid", - "object_id": 34, - "column_id": 3 - }, - { - "name": "nsclass", - "object_id": 34, - "column_id": 4 - }, - { - "name": "status", - "object_id": 34, - "column_id": 5 - }, - { - "name": "type", - "object_id": 34, - "column_id": 6 - }, - { - "name": "pid", - "object_id": 34, - "column_id": 7 - }, - { - "name": "pclass", - "object_id": 34, - "column_id": 8 - }, - { - "name": "intprop", - "object_id": 34, - "column_id": 9 - }, - { - "name": "created", - "object_id": 34, - "column_id": 10 - }, - { - "name": "modified", - "object_id": 34, - "column_id": 11 - }, - { - "name": "status2", - "object_id": 34, - "column_id": 12 - }, - { - "name": "hobt_id", - "object_id": 35, - "column_id": 1 - }, - { - "name": "segment_id", - "object_id": 35, - "column_id": 2 - }, - { - "name": "version", - "object_id": 35, - "column_id": 3 - }, - { - "name": "ds_hobtid", - "object_id": 35, - "column_id": 4 - }, - { - "name": "row_count", - "object_id": 35, - "column_id": 5 - }, - { - "name": "status", - "object_id": 35, - "column_id": 6 - }, - { - "name": "flags", - "object_id": 35, - "column_id": 7 - }, - { - "name": "compressed_reason", - "object_id": 35, - "column_id": 8 - }, - { - "name": "generation", - "object_id": 35, - "column_id": 9 - }, - { - "name": "created_time", - "object_id": 35, - "column_id": 10 - }, - { - "name": "closed_time", - "object_id": 35, - "column_id": 11 - }, - { - "name": "container_id", - "object_id": 35, - "column_id": 12 - }, - { - "name": "blob_id", - "object_id": 35, - "column_id": 13 - }, - { - "name": "metadata_offset", - "object_id": 35, - "column_id": 14 - }, - { - "name": "metadata_size", - "object_id": 35, - "column_id": 15 - }, - { - "name": "data_source_id", - "object_id": 36, - "column_id": 1 - }, - { - "name": "name", - "object_id": 36, - "column_id": 2 - }, - { - "name": "type_desc", - "object_id": 36, - "column_id": 3 - }, - { - "name": "type", - "object_id": 36, - "column_id": 4 - }, - { - "name": "location", - "object_id": 36, - "column_id": 5 - }, - { - "name": "credential_id", - "object_id": 36, - "column_id": 6 - }, - { - "name": "job_tracker_location", - "object_id": 36, - "column_id": 7 - }, - { - "name": "storage_key", - "object_id": 36, - "column_id": 8 - }, - { - "name": "user_name", - "object_id": 36, - "column_id": 9 - }, - { - "name": "shard_map_manager_db", - "object_id": 36, - "column_id": 10 - }, - { - "name": "shard_map_name", - "object_id": 36, - "column_id": 11 - }, - { - "name": "connection_options", - "object_id": 36, - "column_id": 12 - }, - { - "name": "pushdown", - "object_id": 36, - "column_id": 13 - }, - { - "name": "object_id", - "object_id": 37, - "column_id": 1 - }, - { - "name": "data_source_id", - "object_id": 37, - "column_id": 2 - }, - { - "name": "file_format_id", - "object_id": 37, - "column_id": 3 - }, - { - "name": "location", - "object_id": 37, - "column_id": 4 - }, - { - "name": "reject_type", - "object_id": 37, - "column_id": 5 - }, - { - "name": "reject_value", - "object_id": 37, - "column_id": 6 - }, - { - "name": "reject_sample_value", - "object_id": 37, - "column_id": 7 - }, - { - "name": "sharding_dist_type", - "object_id": 37, - "column_id": 8 - }, - { - "name": "sharding_col_id", - "object_id": 37, - "column_id": 9 - }, - { - "name": "source_schema_name", - "object_id": 37, - "column_id": 10 - }, - { - "name": "source_table_name", - "object_id": 37, - "column_id": 11 - }, - { - "name": "rejected_row_location", - "object_id": 37, - "column_id": 12 - }, - { - "name": "file_format_id", - "object_id": 38, - "column_id": 1 - }, - { - "name": "name", - "object_id": 38, - "column_id": 2 - }, - { - "name": "format_type", - "object_id": 38, - "column_id": 3 - }, - { - "name": "field_terminator", - "object_id": 38, - "column_id": 4 - }, - { - "name": "string_delimiter", - "object_id": 38, - "column_id": 5 - }, - { - "name": "date_format", - "object_id": 38, - "column_id": 6 - }, - { - "name": "use_type_default", - "object_id": 38, - "column_id": 7 - }, - { - "name": "serde_method", - "object_id": 38, - "column_id": 8 - }, - { - "name": "row_terminator", - "object_id": 38, - "column_id": 9 - }, - { - "name": "encoding", - "object_id": 38, - "column_id": 10 - }, - { - "name": "data_compression", - "object_id": 38, - "column_id": 11 - }, - { - "name": "first_row", - "object_id": 38, - "column_id": 12 - }, - { - "name": "extractor", - "object_id": 38, - "column_id": 13 - }, - { - "name": "null_values", - "object_id": 38, - "column_id": 14 - }, - { - "name": "parser_version", - "object_id": 38, - "column_id": 15 - }, - { - "name": "valclass", - "object_id": 40, - "column_id": 1 - }, - { - "name": "depid", - "object_id": 40, - "column_id": 2 - }, - { - "name": "depsubid", - "object_id": 40, - "column_id": 3 - }, - { - "name": "indepid", - "object_id": 40, - "column_id": 4 - }, - { - "name": "indepsubid", - "object_id": 40, - "column_id": 5 - }, - { - "name": "valnum", - "object_id": 40, - "column_id": 6 - }, - { - "name": "value", - "object_id": 40, - "column_id": 7 - }, - { - "name": "imageval", - "object_id": 40, - "column_id": 8 - }, - { - "name": "id", - "object_id": 41, - "column_id": 1 - }, - { - "name": "number", - "object_id": 41, - "column_id": 2 - }, - { - "name": "colid", - "object_id": 41, - "column_id": 3 - }, - { - "name": "name", - "object_id": 41, - "column_id": 4 - }, - { - "name": "xtype", - "object_id": 41, - "column_id": 5 - }, - { - "name": "utype", - "object_id": 41, - "column_id": 6 - }, - { - "name": "length", - "object_id": 41, - "column_id": 7 - }, - { - "name": "prec", - "object_id": 41, - "column_id": 8 - }, - { - "name": "scale", - "object_id": 41, - "column_id": 9 - }, - { - "name": "collationid", - "object_id": 41, - "column_id": 10 - }, - { - "name": "status", - "object_id": 41, - "column_id": 11 - }, - { - "name": "maxinrow", - "object_id": 41, - "column_id": 12 - }, - { - "name": "xmlns", - "object_id": 41, - "column_id": 13 - }, - { - "name": "dflt", - "object_id": 41, - "column_id": 14 - }, - { - "name": "chk", - "object_id": 41, - "column_id": 15 - }, - { - "name": "idtval", - "object_id": 41, - "column_id": 16 - }, - { - "name": "id", - "object_id": 42, - "column_id": 1 - }, - { - "name": "name", - "object_id": 42, - "column_id": 2 - }, - { - "name": "sid", - "object_id": 42, - "column_id": 3 - }, - { - "name": "status", - "object_id": 42, - "column_id": 4 - }, - { - "name": "type", - "object_id": 42, - "column_id": 5 - }, - { - "name": "crdate", - "object_id": 42, - "column_id": 6 - }, - { - "name": "modate", - "object_id": 42, - "column_id": 7 - }, - { - "name": "dbname", - "object_id": 42, - "column_id": 8 - }, - { - "name": "lang", - "object_id": 42, - "column_id": 9 - }, - { - "name": "pwdhash", - "object_id": 42, - "column_id": 10 - }, - { - "name": "tenantid", - "object_id": 42, - "column_id": 11 - }, - { - "name": "onpremsid", - "object_id": 42, - "column_id": 12 - }, - { - "name": "externaloid", - "object_id": 42, - "column_id": 13 - }, - { - "name": "id", - "object_id": 43, - "column_id": 1 - }, - { - "name": "name", - "object_id": 43, - "column_id": 2 - }, - { - "name": "product", - "object_id": 43, - "column_id": 3 - }, - { - "name": "provider", - "object_id": 43, - "column_id": 4 - }, - { - "name": "status", - "object_id": 43, - "column_id": 5 - }, - { - "name": "modate", - "object_id": 43, - "column_id": 6 - }, - { - "name": "catalog", - "object_id": 43, - "column_id": 7 - }, - { - "name": "cid", - "object_id": 43, - "column_id": 8 - }, - { - "name": "connecttimeout", - "object_id": 43, - "column_id": 9 - }, - { - "name": "querytimeout", - "object_id": 43, - "column_id": 10 - }, - { - "name": "class", - "object_id": 44, - "column_id": 1 - }, - { - "name": "id", - "object_id": 44, - "column_id": 2 - }, - { - "name": "name", - "object_id": 44, - "column_id": 3 - }, - { - "name": "nsid", - "object_id": 44, - "column_id": 4 - }, - { - "name": "status", - "object_id": 44, - "column_id": 5 - }, - { - "name": "intprop", - "object_id": 44, - "column_id": 6 - }, - { - "name": "created", - "object_id": 44, - "column_id": 7 - }, - { - "name": "modified", - "object_id": 44, - "column_id": 8 - }, - { - "name": "id", - "object_id": 45, - "column_id": 1 - }, - { - "name": "msglangid", - "object_id": 45, - "column_id": 2 - }, - { - "name": "severity", - "object_id": 45, - "column_id": 3 - }, - { - "name": "status", - "object_id": 45, - "column_id": 4 - }, - { - "name": "text", - "object_id": 45, - "column_id": 5 - }, - { - "name": "id", - "object_id": 46, - "column_id": 1 - }, - { - "name": "name", - "object_id": 46, - "column_id": 2 - }, - { - "name": "issuer", - "object_id": 46, - "column_id": 3 - }, - { - "name": "snum", - "object_id": 46, - "column_id": 4 - }, - { - "name": "thumbprint", - "object_id": 46, - "column_id": 5 - }, - { - "name": "pkey", - "object_id": 46, - "column_id": 6 - }, - { - "name": "encrtype", - "object_id": 46, - "column_id": 7 - }, - { - "name": "cert", - "object_id": 46, - "column_id": 8 - }, - { - "name": "status", - "object_id": 46, - "column_id": 9 - }, - { - "name": "lastpkeybackup", - "object_id": 46, - "column_id": 10 - }, - { - "name": "srvid", - "object_id": 47, - "column_id": 1 - }, - { - "name": "name", - "object_id": 47, - "column_id": 2 - }, - { - "name": "lgnid", - "object_id": 47, - "column_id": 3 - }, - { - "name": "status", - "object_id": 47, - "column_id": 4 - }, - { - "name": "modate", - "object_id": 47, - "column_id": 5 - }, - { - "name": "srvid", - "object_id": 48, - "column_id": 1 - }, - { - "name": "lgnid", - "object_id": 48, - "column_id": 2 - }, - { - "name": "name", - "object_id": 48, - "column_id": 3 - }, - { - "name": "status", - "object_id": 48, - "column_id": 4 - }, - { - "name": "modate", - "object_id": 48, - "column_id": 5 - }, - { - "name": "pwdhash", - "object_id": 48, - "column_id": 6 - }, - { - "name": "class", - "object_id": 49, - "column_id": 1 - }, - { - "name": "id", - "object_id": 49, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 49, - "column_id": 3 - }, - { - "name": "name", - "object_id": 49, - "column_id": 4 - }, - { - "name": "value", - "object_id": 49, - "column_id": 5 - }, - { - "name": "id", - "object_id": 50, - "column_id": 1 - }, - { - "name": "schid", - "object_id": 50, - "column_id": 2 - }, - { - "name": "name", - "object_id": 50, - "column_id": 3 - }, - { - "name": "xtype", - "object_id": 50, - "column_id": 4 - }, - { - "name": "length", - "object_id": 50, - "column_id": 5 - }, - { - "name": "prec", - "object_id": 50, - "column_id": 6 - }, - { - "name": "scale", - "object_id": 50, - "column_id": 7 - }, - { - "name": "collationid", - "object_id": 50, - "column_id": 8 - }, - { - "name": "status", - "object_id": 50, - "column_id": 9 - }, - { - "name": "created", - "object_id": 50, - "column_id": 10 - }, - { - "name": "modified", - "object_id": 50, - "column_id": 11 - }, - { - "name": "dflt", - "object_id": 50, - "column_id": 12 - }, - { - "name": "chk", - "object_id": 50, - "column_id": 13 - }, - { - "name": "class", - "object_id": 51, - "column_id": 1 - }, - { - "name": "idmajor", - "object_id": 51, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 51, - "column_id": 3 - }, - { - "name": "name", - "object_id": 51, - "column_id": 4 - }, - { - "name": "xtype", - "object_id": 51, - "column_id": 5 - }, - { - "name": "utype", - "object_id": 51, - "column_id": 6 - }, - { - "name": "length", - "object_id": 51, - "column_id": 7 - }, - { - "name": "prec", - "object_id": 51, - "column_id": 8 - }, - { - "name": "scale", - "object_id": 51, - "column_id": 9 - }, - { - "name": "collationid", - "object_id": 51, - "column_id": 10 - }, - { - "name": "status", - "object_id": 51, - "column_id": 11 - }, - { - "name": "intprop", - "object_id": 51, - "column_id": 12 - }, - { - "name": "id", - "object_id": 54, - "column_id": 1 - }, - { - "name": "indid", - "object_id": 54, - "column_id": 2 - }, - { - "name": "name", - "object_id": 54, - "column_id": 3 - }, - { - "name": "status", - "object_id": 54, - "column_id": 4 - }, - { - "name": "intprop", - "object_id": 54, - "column_id": 5 - }, - { - "name": "fillfact", - "object_id": 54, - "column_id": 6 - }, - { - "name": "type", - "object_id": 54, - "column_id": 7 - }, - { - "name": "tinyprop", - "object_id": 54, - "column_id": 8 - }, - { - "name": "dataspace", - "object_id": 54, - "column_id": 9 - }, - { - "name": "lobds", - "object_id": 54, - "column_id": 10 - }, - { - "name": "rowset", - "object_id": 54, - "column_id": 11 - }, - { - "name": "idmajor", - "object_id": 55, - "column_id": 1 - }, - { - "name": "idminor", - "object_id": 55, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 55, - "column_id": 3 - }, - { - "name": "status", - "object_id": 55, - "column_id": 4 - }, - { - "name": "intprop", - "object_id": 55, - "column_id": 5 - }, - { - "name": "tinyprop1", - "object_id": 55, - "column_id": 6 - }, - { - "name": "tinyprop2", - "object_id": 55, - "column_id": 7 - }, - { - "name": "tinyprop3", - "object_id": 55, - "column_id": 8 - }, - { - "name": "tinyprop4", - "object_id": 55, - "column_id": 9 - }, - { - "name": "id", - "object_id": 56, - "column_id": 1 - }, - { - "name": "name", - "object_id": 56, - "column_id": 2 - }, - { - "name": "protocol", - "object_id": 56, - "column_id": 3 - }, - { - "name": "type", - "object_id": 56, - "column_id": 4 - }, - { - "name": "bstat", - "object_id": 56, - "column_id": 5 - }, - { - "name": "affinity", - "object_id": 56, - "column_id": 6 - }, - { - "name": "pstat", - "object_id": 56, - "column_id": 7 - }, - { - "name": "tstat", - "object_id": 56, - "column_id": 8 - }, - { - "name": "typeint", - "object_id": 56, - "column_id": 9 - }, - { - "name": "port1", - "object_id": 56, - "column_id": 10 - }, - { - "name": "port2", - "object_id": 56, - "column_id": 11 - }, - { - "name": "site", - "object_id": 56, - "column_id": 12 - }, - { - "name": "dfltns", - "object_id": 56, - "column_id": 13 - }, - { - "name": "wsdlproc", - "object_id": 56, - "column_id": 14 - }, - { - "name": "dfltdb", - "object_id": 56, - "column_id": 15 - }, - { - "name": "authrealm", - "object_id": 56, - "column_id": 16 - }, - { - "name": "dfltdm", - "object_id": 56, - "column_id": 17 - }, - { - "name": "maxconn", - "object_id": 56, - "column_id": 18 - }, - { - "name": "encalg", - "object_id": 56, - "column_id": 19 - }, - { - "name": "authtype", - "object_id": 56, - "column_id": 20 - }, - { - "name": "encryptiontype", - "object_id": 56, - "column_id": 21 - }, - { - "name": "id", - "object_id": 57, - "column_id": 1 - }, - { - "name": "nmspace", - "object_id": 57, - "column_id": 2 - }, - { - "name": "alias", - "object_id": 57, - "column_id": 3 - }, - { - "name": "objname", - "object_id": 57, - "column_id": 4 - }, - { - "name": "status", - "object_id": 57, - "column_id": 5 - }, - { - "name": "class", - "object_id": 58, - "column_id": 1 - }, - { - "name": "id", - "object_id": 58, - "column_id": 2 - }, - { - "name": "nsid", - "object_id": 58, - "column_id": 3 - }, - { - "name": "name", - "object_id": 58, - "column_id": 4 - }, - { - "name": "status", - "object_id": 58, - "column_id": 5 - }, - { - "name": "type", - "object_id": 58, - "column_id": 6 - }, - { - "name": "intprop", - "object_id": 58, - "column_id": 7 - }, - { - "name": "created", - "object_id": 58, - "column_id": 8 - }, - { - "name": "modified", - "object_id": 58, - "column_id": 9 - }, - { - "name": "class", - "object_id": 59, - "column_id": 1 - }, - { - "name": "id", - "object_id": 59, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 59, - "column_id": 3 - }, - { - "name": "grantee", - "object_id": 59, - "column_id": 4 - }, - { - "name": "audit_spec_id", - "object_id": 59, - "column_id": 5 - }, - { - "name": "type", - "object_id": 59, - "column_id": 6 - }, - { - "name": "state", - "object_id": 59, - "column_id": 7 - }, - { - "name": "valclass", - "object_id": 60, - "column_id": 1 - }, - { - "name": "objid", - "object_id": 60, - "column_id": 2 - }, - { - "name": "subobjid", - "object_id": 60, - "column_id": 3 - }, - { - "name": "valnum", - "object_id": 60, - "column_id": 4 - }, - { - "name": "value", - "object_id": 60, - "column_id": 5 - }, - { - "name": "imageval", - "object_id": 60, - "column_id": 6 - }, - { - "name": "hobt_id", - "object_id": 62, - "column_id": 1 - }, - { - "name": "column_id", - "object_id": 62, - "column_id": 2 - }, - { - "name": "segment_id", - "object_id": 62, - "column_id": 3 - }, - { - "name": "version", - "object_id": 62, - "column_id": 4 - }, - { - "name": "encoding_type", - "object_id": 62, - "column_id": 5 - }, - { - "name": "row_count", - "object_id": 62, - "column_id": 6 - }, - { - "name": "status", - "object_id": 62, - "column_id": 7 - }, - { - "name": "base_id", - "object_id": 62, - "column_id": 8 - }, - { - "name": "magnitude", - "object_id": 62, - "column_id": 9 - }, - { - "name": "primary_dictionary_id", - "object_id": 62, - "column_id": 10 - }, - { - "name": "secondary_dictionary_id", - "object_id": 62, - "column_id": 11 - }, - { - "name": "min_data_id", - "object_id": 62, - "column_id": 12 - }, - { - "name": "max_data_id", - "object_id": 62, - "column_id": 13 - }, - { - "name": "null_value", - "object_id": 62, - "column_id": 14 - }, - { - "name": "on_disk_size", - "object_id": 62, - "column_id": 15 - }, - { - "name": "data_ptr", - "object_id": 62, - "column_id": 16 - }, - { - "name": "container_id", - "object_id": 62, - "column_id": 17 - }, - { - "name": "bloom_filter_md", - "object_id": 62, - "column_id": 18 - }, - { - "name": "bloom_filter_data_ptr", - "object_id": 62, - "column_id": 19 - }, - { - "name": "collation_id", - "object_id": 62, - "column_id": 20 - }, - { - "name": "min_deep_data", - "object_id": 62, - "column_id": 21 - }, - { - "name": "max_deep_data", - "object_id": 62, - "column_id": 22 - }, - { - "name": "hobt_id", - "object_id": 63, - "column_id": 1 - }, - { - "name": "column_id", - "object_id": 63, - "column_id": 2 - }, - { - "name": "dictionary_id", - "object_id": 63, - "column_id": 3 - }, - { - "name": "version", - "object_id": 63, - "column_id": 4 - }, - { - "name": "type", - "object_id": 63, - "column_id": 5 - }, - { - "name": "flags", - "object_id": 63, - "column_id": 6 - }, - { - "name": "last_id", - "object_id": 63, - "column_id": 7 - }, - { - "name": "entry_count", - "object_id": 63, - "column_id": 8 - }, - { - "name": "on_disk_size", - "object_id": 63, - "column_id": 9 - }, - { - "name": "data_ptr", - "object_id": 63, - "column_id": 10 - }, - { - "name": "container_id", - "object_id": 63, - "column_id": 11 - }, - { - "name": "class", - "object_id": 64, - "column_id": 1 - }, - { - "name": "id", - "object_id": 64, - "column_id": 2 - }, - { - "name": "name", - "object_id": 64, - "column_id": 3 - }, - { - "name": "status", - "object_id": 64, - "column_id": 4 - }, - { - "name": "type", - "object_id": 64, - "column_id": 5 - }, - { - "name": "intprop", - "object_id": 64, - "column_id": 6 - }, - { - "name": "created", - "object_id": 64, - "column_id": 7 - }, - { - "name": "modified", - "object_id": 64, - "column_id": 8 - }, - { - "name": "class", - "object_id": 65, - "column_id": 1 - }, - { - "name": "objid", - "object_id": 65, - "column_id": 2 - }, - { - "name": "indexid", - "object_id": 65, - "column_id": 3 - }, - { - "name": "rowsetnum", - "object_id": 65, - "column_id": 4 - }, - { - "name": "rowsetid", - "object_id": 65, - "column_id": 5 - }, - { - "name": "status", - "object_id": 65, - "column_id": 6 - }, - { - "name": "id", - "object_id": 67, - "column_id": 1 - }, - { - "name": "name", - "object_id": 67, - "column_id": 2 - }, - { - "name": "scid", - "object_id": 67, - "column_id": 3 - }, - { - "name": "remsvc", - "object_id": 67, - "column_id": 4 - }, - { - "name": "status", - "object_id": 67, - "column_id": 5 - }, - { - "name": "dlgid", - "object_id": 68, - "column_id": 1 - }, - { - "name": "finitiator", - "object_id": 68, - "column_id": 2 - }, - { - "name": "tosvc", - "object_id": 68, - "column_id": 3 - }, - { - "name": "tobrkrinst", - "object_id": 68, - "column_id": 4 - }, - { - "name": "fromsvc", - "object_id": 68, - "column_id": 5 - }, - { - "name": "frombrkrinst", - "object_id": 68, - "column_id": 6 - }, - { - "name": "svccontr", - "object_id": 68, - "column_id": 7 - }, - { - "name": "msgseqnum", - "object_id": 68, - "column_id": 8 - }, - { - "name": "msgtype", - "object_id": 68, - "column_id": 9 - }, - { - "name": "unackmfn", - "object_id": 68, - "column_id": 10 - }, - { - "name": "status", - "object_id": 68, - "column_id": 11 - }, - { - "name": "enqtime", - "object_id": 68, - "column_id": 12 - }, - { - "name": "rsndtime", - "object_id": 68, - "column_id": 13 - }, - { - "name": "dlgerr", - "object_id": 68, - "column_id": 14 - }, - { - "name": "msgid", - "object_id": 68, - "column_id": 15 - }, - { - "name": "hdrpartlen", - "object_id": 68, - "column_id": 16 - }, - { - "name": "hdrseclen", - "object_id": 68, - "column_id": 17 - }, - { - "name": "msgenc", - "object_id": 68, - "column_id": 18 - }, - { - "name": "msgbodylen", - "object_id": 68, - "column_id": 19 - }, - { - "name": "msgbody", - "object_id": 68, - "column_id": 20 - }, - { - "name": "msgref", - "object_id": 68, - "column_id": 21 - }, - { - "name": "id", - "object_id": 69, - "column_id": 1 - }, - { - "name": "name", - "object_id": 69, - "column_id": 2 - }, - { - "name": "remsvc", - "object_id": 69, - "column_id": 3 - }, - { - "name": "brkrinst", - "object_id": 69, - "column_id": 4 - }, - { - "name": "addr", - "object_id": 69, - "column_id": 5 - }, - { - "name": "miraddr", - "object_id": 69, - "column_id": 6 - }, - { - "name": "lifetime", - "object_id": 69, - "column_id": 7 - }, - { - "name": "id", - "object_id": 71, - "column_id": 1 - }, - { - "name": "service_id", - "object_id": 71, - "column_id": 2 - }, - { - "name": "status", - "object_id": 71, - "column_id": 3 - }, - { - "name": "refcount", - "object_id": 71, - "column_id": 4 - }, - { - "name": "handle", - "object_id": 72, - "column_id": 1 - }, - { - "name": "diagid", - "object_id": 72, - "column_id": 2 - }, - { - "name": "initiator", - "object_id": 72, - "column_id": 3 - }, - { - "name": "sendseq", - "object_id": 72, - "column_id": 4 - }, - { - "name": "sendxact", - "object_id": 72, - "column_id": 5 - }, - { - "name": "diagid", - "object_id": 73, - "column_id": 1 - }, - { - "name": "initiator", - "object_id": 73, - "column_id": 2 - }, - { - "name": "handle", - "object_id": 73, - "column_id": 3 - }, - { - "name": "rcvseq", - "object_id": 73, - "column_id": 4 - }, - { - "name": "rcvfrag", - "object_id": 73, - "column_id": 5 - }, - { - "name": "status", - "object_id": 73, - "column_id": 6 - }, - { - "name": "state", - "object_id": 73, - "column_id": 7 - }, - { - "name": "lifetime", - "object_id": 73, - "column_id": 8 - }, - { - "name": "contract", - "object_id": 73, - "column_id": 9 - }, - { - "name": "svcid", - "object_id": 73, - "column_id": 10 - }, - { - "name": "convgroup", - "object_id": 73, - "column_id": 11 - }, - { - "name": "sysseq", - "object_id": 73, - "column_id": 12 - }, - { - "name": "enddlgseq", - "object_id": 73, - "column_id": 13 - }, - { - "name": "firstoorder", - "object_id": 73, - "column_id": 14 - }, - { - "name": "lastoorder", - "object_id": 73, - "column_id": 15 - }, - { - "name": "lastoorderfr", - "object_id": 73, - "column_id": 16 - }, - { - "name": "dlgtimer", - "object_id": 73, - "column_id": 17 - }, - { - "name": "dlgopened", - "object_id": 73, - "column_id": 18 - }, - { - "name": "princid", - "object_id": 73, - "column_id": 19 - }, - { - "name": "outseskey", - "object_id": 73, - "column_id": 20 - }, - { - "name": "outseskeyid", - "object_id": 73, - "column_id": 21 - }, - { - "name": "farprincid", - "object_id": 73, - "column_id": 22 - }, - { - "name": "inseskey", - "object_id": 73, - "column_id": 23 - }, - { - "name": "inseskeyid", - "object_id": 73, - "column_id": 24 - }, - { - "name": "farsvc", - "object_id": 73, - "column_id": 25 - }, - { - "name": "farbrkrinst", - "object_id": 73, - "column_id": 26 - }, - { - "name": "priority", - "object_id": 73, - "column_id": 27 - }, - { - "name": "class", - "object_id": 74, - "column_id": 1 - }, - { - "name": "depid", - "object_id": 74, - "column_id": 2 - }, - { - "name": "depsubid", - "object_id": 74, - "column_id": 3 - }, - { - "name": "indepid", - "object_id": 74, - "column_id": 4 - }, - { - "name": "indepsubid", - "object_id": 74, - "column_id": 5 - }, - { - "name": "status", - "object_id": 74, - "column_id": 6 - }, - { - "name": "class", - "object_id": 75, - "column_id": 1 - }, - { - "name": "depid", - "object_id": 75, - "column_id": 2 - }, - { - "name": "depsubid", - "object_id": 75, - "column_id": 3 - }, - { - "name": "indepid", - "object_id": 75, - "column_id": 4 - }, - { - "name": "indepsubid", - "object_id": 75, - "column_id": 5 - }, - { - "name": "status", - "object_id": 75, - "column_id": 6 - }, - { - "name": "class", - "object_id": 78, - "column_id": 1 - }, - { - "name": "id", - "object_id": 78, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 78, - "column_id": 3 - }, - { - "name": "guid", - "object_id": 78, - "column_id": 4 - }, - { - "name": "status", - "object_id": 78, - "column_id": 5 - }, - { - "name": "id", - "object_id": 79, - "column_id": 1 - }, - { - "name": "lsn", - "object_id": 79, - "column_id": 2 - }, - { - "name": "epoch", - "object_id": 79, - "column_id": 3 - }, - { - "name": "csn", - "object_id": 79, - "column_id": 4 - }, - { - "name": "created", - "object_id": 79, - "column_id": 5 - }, - { - "name": "lsid", - "object_id": 80, - "column_id": 1 - }, - { - "name": "iname", - "object_id": 80, - "column_id": 2 - }, - { - "name": "ipipename", - "object_id": 80, - "column_id": 3 - }, - { - "name": "pid", - "object_id": 80, - "column_id": 4 - }, - { - "name": "status", - "object_id": 80, - "column_id": 5 - }, - { - "name": "crdate", - "object_id": 80, - "column_id": 6 - }, - { - "name": "modate", - "object_id": 80, - "column_id": 7 - }, - { - "name": "sysdbpath", - "object_id": 80, - "column_id": 8 - }, - { - "name": "cprelid", - "object_id": 82, - "column_id": 1 - }, - { - "name": "fragid", - "object_id": 82, - "column_id": 2 - }, - { - "name": "fragobjid", - "object_id": 82, - "column_id": 3 - }, - { - "name": "ts", - "object_id": 82, - "column_id": 4 - }, - { - "name": "status", - "object_id": 82, - "column_id": 5 - }, - { - "name": "datasize", - "object_id": 82, - "column_id": 6 - }, - { - "name": "itemcnt", - "object_id": 82, - "column_id": 7 - }, - { - "name": "rowcnt", - "object_id": 82, - "column_id": 8 - }, - { - "name": "database_id", - "object_id": 84, - "column_id": 1 - }, - { - "name": "register_date", - "object_id": 84, - "column_id": 2 - }, - { - "name": "registered_by", - "object_id": 84, - "column_id": 3 - }, - { - "name": "version", - "object_id": 84, - "column_id": 4 - }, - { - "name": "fileguid", - "object_id": 84, - "column_id": 5 - }, - { - "name": "stoplistid", - "object_id": 85, - "column_id": 1 - }, - { - "name": "stopword", - "object_id": 85, - "column_id": 2 - }, - { - "name": "lcid", - "object_id": 85, - "column_id": 3 - }, - { - "name": "status", - "object_id": 85, - "column_id": 4 - }, - { - "name": "property_list_id", - "object_id": 86, - "column_id": 1 - }, - { - "name": "property_id", - "object_id": 86, - "column_id": 2 - }, - { - "name": "property_name", - "object_id": 86, - "column_id": 3 - }, - { - "name": "guid_identifier", - "object_id": 86, - "column_id": 4 - }, - { - "name": "int_identifier", - "object_id": 86, - "column_id": 5 - }, - { - "name": "string_description", - "object_id": 86, - "column_id": 6 - }, - { - "name": "msgref", - "object_id": 87, - "column_id": 1 - }, - { - "name": "count", - "object_id": 87, - "column_id": 2 - }, - { - "name": "msgbody", - "object_id": 87, - "column_id": 3 - }, - { - "name": "id", - "object_id": 89, - "column_id": 1 - }, - { - "name": "tgid", - "object_id": 89, - "column_id": 2 - }, - { - "name": "low", - "object_id": 89, - "column_id": 3 - }, - { - "name": "high", - "object_id": 89, - "column_id": 4 - }, - { - "name": "rowcnt", - "object_id": 89, - "column_id": 5 - }, - { - "name": "size", - "object_id": 89, - "column_id": 6 - }, - { - "name": "csn", - "object_id": 89, - "column_id": 7 - }, - { - "name": "epoch", - "object_id": 89, - "column_id": 8 - }, - { - "name": "status", - "object_id": 89, - "column_id": 9 - }, - { - "name": "history", - "object_id": 89, - "column_id": 10 - }, - { - "name": "created", - "object_id": 89, - "column_id": 11 - }, - { - "name": "modified", - "object_id": 89, - "column_id": 12 - }, - { - "name": "qid", - "object_id": 90, - "column_id": 1 - }, - { - "name": "hash", - "object_id": 90, - "column_id": 2 - }, - { - "name": "nid", - "object_id": 90, - "column_id": 3 - }, - { - "name": "name", - "object_id": 90, - "column_id": 4 - }, - { - "name": "id", - "object_id": 91, - "column_id": 1 - }, - { - "name": "xsdid", - "object_id": 91, - "column_id": 2 - }, - { - "name": "uriord", - "object_id": 91, - "column_id": 3 - }, - { - "name": "qual", - "object_id": 91, - "column_id": 4 - }, - { - "name": "nameid", - "object_id": 91, - "column_id": 5 - }, - { - "name": "symspace", - "object_id": 91, - "column_id": 6 - }, - { - "name": "nmscope", - "object_id": 91, - "column_id": 7 - }, - { - "name": "kind", - "object_id": 91, - "column_id": 8 - }, - { - "name": "deriv", - "object_id": 91, - "column_id": 9 - }, - { - "name": "status", - "object_id": 91, - "column_id": 10 - }, - { - "name": "enum", - "object_id": 91, - "column_id": 11 - }, - { - "name": "defval", - "object_id": 91, - "column_id": 12 - }, - { - "name": "compid", - "object_id": 92, - "column_id": 1 - }, - { - "name": "ord", - "object_id": 92, - "column_id": 2 - }, - { - "name": "kind", - "object_id": 92, - "column_id": 3 - }, - { - "name": "status", - "object_id": 92, - "column_id": 4 - }, - { - "name": "dflt", - "object_id": 92, - "column_id": 5 - }, - { - "name": "placingid", - "object_id": 93, - "column_id": 1 - }, - { - "name": "ordinal", - "object_id": 93, - "column_id": 2 - }, - { - "name": "placedid", - "object_id": 93, - "column_id": 3 - }, - { - "name": "status", - "object_id": 93, - "column_id": 4 - }, - { - "name": "minoccur", - "object_id": 93, - "column_id": 5 - }, - { - "name": "maxoccur", - "object_id": 93, - "column_id": 6 - }, - { - "name": "defval", - "object_id": 93, - "column_id": 7 - }, - { - "name": "class", - "object_id": 94, - "column_id": 1 - }, - { - "name": "id", - "object_id": 94, - "column_id": 2 - }, - { - "name": "thumbprint", - "object_id": 94, - "column_id": 3 - }, - { - "name": "type", - "object_id": 94, - "column_id": 4 - }, - { - "name": "crypto", - "object_id": 94, - "column_id": 5 - }, - { - "name": "status", - "object_id": 94, - "column_id": 6 - }, - { - "name": "id", - "object_id": 95, - "column_id": 1 - }, - { - "name": "name", - "object_id": 95, - "column_id": 2 - }, - { - "name": "thumbprint", - "object_id": 95, - "column_id": 3 - }, - { - "name": "bitlength", - "object_id": 95, - "column_id": 4 - }, - { - "name": "algorithm", - "object_id": 95, - "column_id": 5 - }, - { - "name": "modified", - "object_id": 95, - "column_id": 6 - }, - { - "name": "pkey", - "object_id": 95, - "column_id": 7 - }, - { - "name": "encrtype", - "object_id": 95, - "column_id": 8 - }, - { - "name": "pukey", - "object_id": 95, - "column_id": 9 - }, - { - "name": "id", - "object_id": 96, - "column_id": 1 - }, - { - "name": "name", - "object_id": 96, - "column_id": 2 - }, - { - "name": "scopetype", - "object_id": 96, - "column_id": 3 - }, - { - "name": "scopeid", - "object_id": 96, - "column_id": 4 - }, - { - "name": "hash", - "object_id": 96, - "column_id": 5 - }, - { - "name": "status", - "object_id": 96, - "column_id": 6 - }, - { - "name": "created", - "object_id": 96, - "column_id": 7 - }, - { - "name": "modified", - "object_id": 96, - "column_id": 8 - }, - { - "name": "batchtext", - "object_id": 96, - "column_id": 9 - }, - { - "name": "paramorhinttext", - "object_id": 96, - "column_id": 10 - }, - { - "name": "class", - "object_id": 97, - "column_id": 1 - }, - { - "name": "idmajor", - "object_id": 97, - "column_id": 2 - }, - { - "name": "subid", - "object_id": 97, - "column_id": 3 - }, - { - "name": "name", - "object_id": 97, - "column_id": 4 - }, - { - "name": "status", - "object_id": 97, - "column_id": 5 - }, - { - "name": "intprop", - "object_id": 97, - "column_id": 6 - }, - { - "name": "depclass", - "object_id": 98, - "column_id": 1 - }, - { - "name": "depid", - "object_id": 98, - "column_id": 2 - }, - { - "name": "indepclass", - "object_id": 98, - "column_id": 3 - }, - { - "name": "indepname", - "object_id": 98, - "column_id": 4 - }, - { - "name": "indepschema", - "object_id": 98, - "column_id": 5 - }, - { - "name": "indepdb", - "object_id": 98, - "column_id": 6 - }, - { - "name": "indepserver", - "object_id": 98, - "column_id": 7 - }, - { - "name": "number", - "object_id": 98, - "column_id": 8 - }, - { - "name": "status", - "object_id": 98, - "column_id": 9 - }, - { - "name": "job_id", - "object_id": 101575400, - "column_id": 1 - }, - { - "name": "name", - "object_id": 101575400, - "column_id": 2 - }, - { - "name": "enabled", - "object_id": 101575400, - "column_id": 3 - }, - { - "name": "description", - "object_id": 101575400, - "column_id": 4 - }, - { - "name": "start_step_id", - "object_id": 101575400, - "column_id": 5 - }, - { - "name": "notify_level_eventlog", - "object_id": 101575400, - "column_id": 6 - }, - { - "name": "delete_level", - "object_id": 101575400, - "column_id": 7 - }, - { - "name": "date_created", - "object_id": 101575400, - "column_id": 8 - }, - { - "name": "date_modified", - "object_id": 101575400, - "column_id": 9 - }, - { - "name": "job_id", - "object_id": 117575457, - "column_id": 1 - }, - { - "name": "step_id", - "object_id": 117575457, - "column_id": 2 - }, - { - "name": "step_name", - "object_id": 117575457, - "column_id": 3 - }, - { - "name": "subsystem", - "object_id": 117575457, - "column_id": 4 - }, - { - "name": "command", - "object_id": 117575457, - "column_id": 5 - }, - { - "name": "flags", - "object_id": 117575457, - "column_id": 6 - }, - { - "name": "additional_parameters", - "object_id": 117575457, - "column_id": 7 - }, - { - "name": "cmdexec_success_code", - "object_id": 117575457, - "column_id": 8 - }, - { - "name": "on_success_action", - "object_id": 117575457, - "column_id": 9 - }, - { - "name": "on_success_step_id", - "object_id": 117575457, - "column_id": 10 - }, - { - "name": "on_fail_action", - "object_id": 117575457, - "column_id": 11 - }, - { - "name": "on_fail_step_id", - "object_id": 117575457, - "column_id": 12 - }, - { - "name": "server", - "object_id": 117575457, - "column_id": 13 - }, - { - "name": "database_name", - "object_id": 117575457, - "column_id": 14 - }, - { - "name": "database_user_name", - "object_id": 117575457, - "column_id": 15 - }, - { - "name": "retry_attempts", - "object_id": 117575457, - "column_id": 16 - }, - { - "name": "retry_interval", - "object_id": 117575457, - "column_id": 17 - }, - { - "name": "os_run_priority", - "object_id": 117575457, - "column_id": 18 - }, - { - "name": "output_file_name", - "object_id": 117575457, - "column_id": 19 - }, - { - "name": "last_run_outcome", - "object_id": 117575457, - "column_id": 20 - }, - { - "name": "last_run_duration", - "object_id": 117575457, - "column_id": 21 - }, - { - "name": "last_run_retries", - "object_id": 117575457, - "column_id": 22 - }, - { - "name": "last_run_date", - "object_id": 117575457, - "column_id": 23 - }, - { - "name": "last_run_time", - "object_id": 117575457, - "column_id": 24 - }, - { - "name": "step_uid", - "object_id": 117575457, - "column_id": 25 - }, - { - "name": "instance_id", - "object_id": 133575514, - "column_id": 1 - }, - { - "name": "job_id", - "object_id": 133575514, - "column_id": 2 - }, - { - "name": "step_id", - "object_id": 133575514, - "column_id": 3 - }, - { - "name": "sql_message_id", - "object_id": 133575514, - "column_id": 4 - }, - { - "name": "sql_severity", - "object_id": 133575514, - "column_id": 5 - }, - { - "name": "message", - "object_id": 133575514, - "column_id": 6 - }, - { - "name": "run_status", - "object_id": 133575514, - "column_id": 7 - }, - { - "name": "run_date", - "object_id": 133575514, - "column_id": 8 - }, - { - "name": "run_time", - "object_id": 133575514, - "column_id": 9 - }, - { - "name": "run_duration", - "object_id": 133575514, - "column_id": 10 - }, - { - "name": "operator_id_emailed", - "object_id": 133575514, - "column_id": 11 - }, - { - "name": "operator_id_paged", - "object_id": 133575514, - "column_id": 12 - }, - { - "name": "retries_attempted", - "object_id": 133575514, - "column_id": 13 - }, - { - "name": "log_id", - "object_id": 149575571, - "column_id": 1 - }, - { - "name": "log_text", - "object_id": 149575571, - "column_id": 2 - }, - { - "name": "date_created", - "object_id": 149575571, - "column_id": 3 - }, - { - "name": "step_uid", - "object_id": 149575571, - "column_id": 4 - }, - { - "name": "query_text_id", - "object_id": 165575628, - "column_id": 1 - }, - { - "name": "query_sql_text", - "object_id": 165575628, - "column_id": 2 - }, - { - "name": "statement_sql_handle", - "object_id": 165575628, - "column_id": 3 - }, - { - "name": "is_part_of_encrypted_module", - "object_id": 165575628, - "column_id": 4 - }, - { - "name": "has_restricted_text", - "object_id": 165575628, - "column_id": 5 - }, - { - "name": "query_template_hash", - "object_id": 165575628, - "column_id": 6 - }, - { - "name": "query_id", - "object_id": 181575685, - "column_id": 1 - }, - { - "name": "query_text_id", - "object_id": 181575685, - "column_id": 2 - }, - { - "name": "context_settings_id", - "object_id": 181575685, - "column_id": 3 - }, - { - "name": "object_id", - "object_id": 181575685, - "column_id": 4 - }, - { - "name": "batch_sql_handle", - "object_id": 181575685, - "column_id": 5 - }, - { - "name": "query_hash", - "object_id": 181575685, - "column_id": 6 - }, - { - "name": "is_internal_query", - "object_id": 181575685, - "column_id": 7 - }, - { - "name": "query_param_type", - "object_id": 181575685, - "column_id": 8 - }, - { - "name": "initial_compile_start_time", - "object_id": 181575685, - "column_id": 9 - }, - { - "name": "last_compile_start_time", - "object_id": 181575685, - "column_id": 10 - }, - { - "name": "last_execution_time", - "object_id": 181575685, - "column_id": 11 - }, - { - "name": "last_compile_batch_sql_handle", - "object_id": 181575685, - "column_id": 12 - }, - { - "name": "last_compile_batch_offset_start", - "object_id": 181575685, - "column_id": 13 - }, - { - "name": "last_compile_batch_offset_end", - "object_id": 181575685, - "column_id": 14 - }, - { - "name": "compile_count", - "object_id": 181575685, - "column_id": 15 - }, - { - "name": "total_compile_duration", - "object_id": 181575685, - "column_id": 16 - }, - { - "name": "last_compile_duration", - "object_id": 181575685, - "column_id": 17 - }, - { - "name": "total_parse_duration", - "object_id": 181575685, - "column_id": 18 - }, - { - "name": "last_parse_duration", - "object_id": 181575685, - "column_id": 19 - }, - { - "name": "total_parse_cpu_time", - "object_id": 181575685, - "column_id": 20 - }, - { - "name": "last_parse_cpu_time", - "object_id": 181575685, - "column_id": 21 - }, - { - "name": "total_bind_duration", - "object_id": 181575685, - "column_id": 22 - }, - { - "name": "last_bind_duration", - "object_id": 181575685, - "column_id": 23 - }, - { - "name": "total_bind_cpu_time", - "object_id": 181575685, - "column_id": 24 - }, - { - "name": "last_bind_cpu_time", - "object_id": 181575685, - "column_id": 25 - }, - { - "name": "total_optimize_duration", - "object_id": 181575685, - "column_id": 26 - }, - { - "name": "last_optimize_duration", - "object_id": 181575685, - "column_id": 27 - }, - { - "name": "total_optimize_cpu_time", - "object_id": 181575685, - "column_id": 28 - }, - { - "name": "last_optimize_cpu_time", - "object_id": 181575685, - "column_id": 29 - }, - { - "name": "total_compile_memory_kb", - "object_id": 181575685, - "column_id": 30 - }, - { - "name": "last_compile_memory_kb", - "object_id": 181575685, - "column_id": 31 - }, - { - "name": "max_compile_memory_kb", - "object_id": 181575685, - "column_id": 32 - }, - { - "name": "status", - "object_id": 181575685, - "column_id": 33 - }, - { - "name": "statement_sql_handle", - "object_id": 181575685, - "column_id": 34 - }, - { - "name": "query_flags", - "object_id": 181575685, - "column_id": 35 - }, - { - "name": "plan_id", - "object_id": 197575742, - "column_id": 1 - }, - { - "name": "query_id", - "object_id": 197575742, - "column_id": 2 - }, - { - "name": "plan_group_id", - "object_id": 197575742, - "column_id": 3 - }, - { - "name": "engine_version", - "object_id": 197575742, - "column_id": 4 - }, - { - "name": "query_plan_hash", - "object_id": 197575742, - "column_id": 5 - }, - { - "name": "query_plan", - "object_id": 197575742, - "column_id": 6 - }, - { - "name": "is_online_index_plan", - "object_id": 197575742, - "column_id": 7 - }, - { - "name": "is_trivial_plan", - "object_id": 197575742, - "column_id": 8 - }, - { - "name": "is_parallel_plan", - "object_id": 197575742, - "column_id": 9 - }, - { - "name": "is_forced_plan", - "object_id": 197575742, - "column_id": 10 - }, - { - "name": "force_failure_count", - "object_id": 197575742, - "column_id": 11 - }, - { - "name": "last_force_failure_reason", - "object_id": 197575742, - "column_id": 12 - }, - { - "name": "count_compiles", - "object_id": 197575742, - "column_id": 13 - }, - { - "name": "initial_compile_start_time", - "object_id": 197575742, - "column_id": 14 - }, - { - "name": "last_compile_start_time", - "object_id": 197575742, - "column_id": 15 - }, - { - "name": "last_execution_time", - "object_id": 197575742, - "column_id": 16 - }, - { - "name": "total_compile_duration", - "object_id": 197575742, - "column_id": 17 - }, - { - "name": "last_compile_duration", - "object_id": 197575742, - "column_id": 18 - }, - { - "name": "compatibility_level", - "object_id": 197575742, - "column_id": 19 - }, - { - "name": "plan_flags", - "object_id": 197575742, - "column_id": 20 - }, - { - "name": "runtime_stats_id", - "object_id": 213575799, - "column_id": 1 - }, - { - "name": "plan_id", - "object_id": 213575799, - "column_id": 2 - }, - { - "name": "runtime_stats_interval_id", - "object_id": 213575799, - "column_id": 3 - }, - { - "name": "execution_type", - "object_id": 213575799, - "column_id": 4 - }, - { - "name": "first_execution_time", - "object_id": 213575799, - "column_id": 5 - }, - { - "name": "last_execution_time", - "object_id": 213575799, - "column_id": 6 - }, - { - "name": "count_executions", - "object_id": 213575799, - "column_id": 7 - }, - { - "name": "total_duration", - "object_id": 213575799, - "column_id": 8 - }, - { - "name": "last_duration", - "object_id": 213575799, - "column_id": 9 - }, - { - "name": "min_duration", - "object_id": 213575799, - "column_id": 10 - }, - { - "name": "max_duration", - "object_id": 213575799, - "column_id": 11 - }, - { - "name": "sumsquare_duration", - "object_id": 213575799, - "column_id": 12 - }, - { - "name": "total_cpu_time", - "object_id": 213575799, - "column_id": 13 - }, - { - "name": "last_cpu_time", - "object_id": 213575799, - "column_id": 14 - }, - { - "name": "min_cpu_time", - "object_id": 213575799, - "column_id": 15 - }, - { - "name": "max_cpu_time", - "object_id": 213575799, - "column_id": 16 - }, - { - "name": "sumsquare_cpu_time", - "object_id": 213575799, - "column_id": 17 - }, - { - "name": "total_logical_io_reads", - "object_id": 213575799, - "column_id": 18 - }, - { - "name": "last_logical_io_reads", - "object_id": 213575799, - "column_id": 19 - }, - { - "name": "min_logical_io_reads", - "object_id": 213575799, - "column_id": 20 - }, - { - "name": "max_logical_io_reads", - "object_id": 213575799, - "column_id": 21 - }, - { - "name": "sumsquare_logical_io_reads", - "object_id": 213575799, - "column_id": 22 - }, - { - "name": "total_logical_io_writes", - "object_id": 213575799, - "column_id": 23 - }, - { - "name": "last_logical_io_writes", - "object_id": 213575799, - "column_id": 24 - }, - { - "name": "min_logical_io_writes", - "object_id": 213575799, - "column_id": 25 - }, - { - "name": "max_logical_io_writes", - "object_id": 213575799, - "column_id": 26 - }, - { - "name": "sumsquare_logical_io_writes", - "object_id": 213575799, - "column_id": 27 - }, - { - "name": "total_physical_io_reads", - "object_id": 213575799, - "column_id": 28 - }, - { - "name": "last_physical_io_reads", - "object_id": 213575799, - "column_id": 29 - }, - { - "name": "min_physical_io_reads", - "object_id": 213575799, - "column_id": 30 - }, - { - "name": "max_physical_io_reads", - "object_id": 213575799, - "column_id": 31 - }, - { - "name": "sumsquare_physical_io_reads", - "object_id": 213575799, - "column_id": 32 - }, - { - "name": "total_clr_time", - "object_id": 213575799, - "column_id": 33 - }, - { - "name": "last_clr_time", - "object_id": 213575799, - "column_id": 34 - }, - { - "name": "min_clr_time", - "object_id": 213575799, - "column_id": 35 - }, - { - "name": "max_clr_time", - "object_id": 213575799, - "column_id": 36 - }, - { - "name": "sumsquare_clr_time", - "object_id": 213575799, - "column_id": 37 - }, - { - "name": "total_dop", - "object_id": 213575799, - "column_id": 38 - }, - { - "name": "last_dop", - "object_id": 213575799, - "column_id": 39 - }, - { - "name": "min_dop", - "object_id": 213575799, - "column_id": 40 - }, - { - "name": "max_dop", - "object_id": 213575799, - "column_id": 41 - }, - { - "name": "sumsquare_dop", - "object_id": 213575799, - "column_id": 42 - }, - { - "name": "total_query_max_used_memory", - "object_id": 213575799, - "column_id": 43 - }, - { - "name": "last_query_max_used_memory", - "object_id": 213575799, - "column_id": 44 - }, - { - "name": "min_query_max_used_memory", - "object_id": 213575799, - "column_id": 45 - }, - { - "name": "max_query_max_used_memory", - "object_id": 213575799, - "column_id": 46 - }, - { - "name": "sumsquare_query_max_used_memory", - "object_id": 213575799, - "column_id": 47 - }, - { - "name": "total_rowcount", - "object_id": 213575799, - "column_id": 48 - }, - { - "name": "last_rowcount", - "object_id": 213575799, - "column_id": 49 - }, - { - "name": "min_rowcount", - "object_id": 213575799, - "column_id": 50 - }, - { - "name": "max_rowcount", - "object_id": 213575799, - "column_id": 51 - }, - { - "name": "sumsquare_rowcount", - "object_id": 213575799, - "column_id": 52 - }, - { - "name": "total_num_physical_io_reads", - "object_id": 213575799, - "column_id": 53 - }, - { - "name": "last_num_physical_io_reads", - "object_id": 213575799, - "column_id": 54 - }, - { - "name": "min_num_physical_io_reads", - "object_id": 213575799, - "column_id": 55 - }, - { - "name": "max_num_physical_io_reads", - "object_id": 213575799, - "column_id": 56 - }, - { - "name": "sumsquare_num_physical_io_reads", - "object_id": 213575799, - "column_id": 57 - }, - { - "name": "total_log_bytes_used", - "object_id": 213575799, - "column_id": 58 - }, - { - "name": "last_log_bytes_used", - "object_id": 213575799, - "column_id": 59 - }, - { - "name": "min_log_bytes_used", - "object_id": 213575799, - "column_id": 60 - }, - { - "name": "max_log_bytes_used", - "object_id": 213575799, - "column_id": 61 - }, - { - "name": "sumsquare_log_bytes_used", - "object_id": 213575799, - "column_id": 62 - }, - { - "name": "total_tempdb_space_used", - "object_id": 213575799, - "column_id": 63 - }, - { - "name": "last_tempdb_space_used", - "object_id": 213575799, - "column_id": 64 - }, - { - "name": "min_tempdb_space_used", - "object_id": 213575799, - "column_id": 65 - }, - { - "name": "max_tempdb_space_used", - "object_id": 213575799, - "column_id": 66 - }, - { - "name": "sumsquare_tempdb_space_used", - "object_id": 213575799, - "column_id": 67 - }, - { - "name": "total_page_server_io_reads", - "object_id": 213575799, - "column_id": 68 - }, - { - "name": "last_page_server_io_reads", - "object_id": 213575799, - "column_id": 69 - }, - { - "name": "min_page_server_io_reads", - "object_id": 213575799, - "column_id": 70 - }, - { - "name": "max_page_server_io_reads", - "object_id": 213575799, - "column_id": 71 - }, - { - "name": "sumsquare_page_server_io_reads", - "object_id": 213575799, - "column_id": 72 - }, - { - "name": "runtime_stats_interval_id", - "object_id": 229575856, - "column_id": 1 - }, - { - "name": "start_time", - "object_id": 229575856, - "column_id": 2 - }, - { - "name": "end_time", - "object_id": 229575856, - "column_id": 3 - }, - { - "name": "comment", - "object_id": 229575856, - "column_id": 4 - }, - { - "name": "context_settings_id", - "object_id": 245575913, - "column_id": 1 - }, - { - "name": "set_options", - "object_id": 245575913, - "column_id": 2 - }, - { - "name": "language_id", - "object_id": 245575913, - "column_id": 3 - }, - { - "name": "date_format", - "object_id": 245575913, - "column_id": 4 - }, - { - "name": "date_first", - "object_id": 245575913, - "column_id": 5 - }, - { - "name": "compatibility_level", - "object_id": 245575913, - "column_id": 6 - }, - { - "name": "status", - "object_id": 245575913, - "column_id": 7 - }, - { - "name": "required_cursor_options", - "object_id": 245575913, - "column_id": 8 - }, - { - "name": "acceptable_cursor_options", - "object_id": 245575913, - "column_id": 9 - }, - { - "name": "merge_action_type", - "object_id": 245575913, - "column_id": 10 - }, - { - "name": "default_schema_id", - "object_id": 245575913, - "column_id": 11 - }, - { - "name": "is_replication_specific", - "object_id": 245575913, - "column_id": 12 - }, - { - "name": "status2", - "object_id": 245575913, - "column_id": 13 - }, - { - "name": "query_hint_id", - "object_id": 261575970, - "column_id": 1 - }, - { - "name": "query_id", - "object_id": 261575970, - "column_id": 2 - }, - { - "name": "context_settings_id", - "object_id": 261575970, - "column_id": 3 - }, - { - "name": "object_id", - "object_id": 261575970, - "column_id": 4 - }, - { - "name": "statement_sql_handle", - "object_id": 261575970, - "column_id": 5 - }, - { - "name": "query_param_type", - "object_id": 261575970, - "column_id": 6 - }, - { - "name": "batch_sql_handle", - "object_id": 261575970, - "column_id": 7 - }, - { - "name": "query_hash", - "object_id": 261575970, - "column_id": 8 - }, - { - "name": "query_hints", - "object_id": 261575970, - "column_id": 9 - }, - { - "name": "query_hints_flags", - "object_id": 261575970, - "column_id": 10 - }, - { - "name": "last_query_hint_failure_reason", - "object_id": 261575970, - "column_id": 11 - }, - { - "name": "query_hint_failure_count", - "object_id": 261575970, - "column_id": 12 - }, - { - "name": "comment", - "object_id": 261575970, - "column_id": 13 - }, - { - "name": "replica_group_id", - "object_id": 261575970, - "column_id": 14 - }, - { - "name": "query_template_id", - "object_id": 277576027, - "column_id": 1 - }, - { - "name": "query_template", - "object_id": 277576027, - "column_id": 2 - }, - { - "name": "query_template_hash", - "object_id": 277576027, - "column_id": 3 - }, - { - "name": "query_param_type", - "object_id": 277576027, - "column_id": 4 - }, - { - "name": "query_template_flags", - "object_id": 277576027, - "column_id": 5 - }, - { - "name": "status", - "object_id": 277576027, - "column_id": 6 - }, - { - "name": "last_parameterization_failure_reason", - "object_id": 277576027, - "column_id": 7 - }, - { - "name": "parameterization_failure_count", - "object_id": 277576027, - "column_id": 8 - }, - { - "name": "comment", - "object_id": 277576027, - "column_id": 9 - }, - { - "name": "wait_stats_id", - "object_id": 293576084, - "column_id": 1 - }, - { - "name": "runtime_stats_interval_id", - "object_id": 293576084, - "column_id": 2 - }, - { - "name": "plan_id", - "object_id": 293576084, - "column_id": 3 - }, - { - "name": "wait_category", - "object_id": 293576084, - "column_id": 4 - }, - { - "name": "execution_type", - "object_id": 293576084, - "column_id": 5 - }, - { - "name": "count_executions", - "object_id": 293576084, - "column_id": 6 - }, - { - "name": "total_query_wait_time_ms", - "object_id": 293576084, - "column_id": 7 - }, - { - "name": "last_query_wait_time_ms", - "object_id": 293576084, - "column_id": 8 - }, - { - "name": "min_query_wait_time_ms", - "object_id": 293576084, - "column_id": 9 - }, - { - "name": "max_query_wait_time_ms", - "object_id": 293576084, - "column_id": 10 - }, - { - "name": "sumsquare_query_wait_time_ms", - "object_id": 293576084, - "column_id": 11 - }, - { - "name": "xdes_ts_push", - "object_id": 309576141, - "column_id": 1 - }, - { - "name": "xdes_ts_tran", - "object_id": 309576141, - "column_id": 2 - }, - { - "name": "subid_push", - "object_id": 309576141, - "column_id": 3 - }, - { - "name": "subid_tran", - "object_id": 309576141, - "column_id": 4 - }, - { - "name": "rowset_id", - "object_id": 309576141, - "column_id": 5 - }, - { - "name": "sec_version_rid", - "object_id": 309576141, - "column_id": 6 - }, - { - "name": "min_len", - "object_id": 309576141, - "column_id": 7 - }, - { - "name": "seq_num", - "object_id": 309576141, - "column_id": 8 - }, - { - "name": "prev_row_in_chain", - "object_id": 309576141, - "column_id": 9 - }, - { - "name": "row_version", - "object_id": 309576141, - "column_id": 10 - }, - { - "name": "xdes_ts_push", - "object_id": 325576198, - "column_id": 1 - }, - { - "name": "xdes_ts_tran", - "object_id": 325576198, - "column_id": 2 - }, - { - "name": "subid_push", - "object_id": 325576198, - "column_id": 3 - }, - { - "name": "subid_tran", - "object_id": 325576198, - "column_id": 4 - }, - { - "name": "rowset_id", - "object_id": 325576198, - "column_id": 5 - }, - { - "name": "sec_version_rid", - "object_id": 325576198, - "column_id": 6 - }, - { - "name": "min_len", - "object_id": 325576198, - "column_id": 7 - }, - { - "name": "seq_num", - "object_id": 325576198, - "column_id": 8 - }, - { - "name": "prev_row_in_chain", - "object_id": 325576198, - "column_id": 9 - }, - { - "name": "row_version", - "object_id": 325576198, - "column_id": 10 - }, - { - "name": "block_id", - "object_id": 341576255, - "column_id": 1 - }, - { - "name": "version", - "object_id": 341576255, - "column_id": 2 - }, - { - "name": "transactions_root_hash", - "object_id": 341576255, - "column_id": 3 - }, - { - "name": "block_size", - "object_id": 341576255, - "column_id": 4 - }, - { - "name": "previous_block_hash", - "object_id": 341576255, - "column_id": 5 - }, - { - "name": "transaction_id", - "object_id": 357576312, - "column_id": 1 - }, - { - "name": "block_id", - "object_id": 357576312, - "column_id": 2 - }, - { - "name": "transaction_ordinal", - "object_id": 357576312, - "column_id": 3 - }, - { - "name": "version", - "object_id": 357576312, - "column_id": 4 - }, - { - "name": "type", - "object_id": 357576312, - "column_id": 5 - }, - { - "name": "commit_ts", - "object_id": 357576312, - "column_id": 6 - }, - { - "name": "table_hashes", - "object_id": 357576312, - "column_id": 7 - }, - { - "name": "commit_LSN", - "object_id": 357576312, - "column_id": 8 - }, - { - "name": "transaction_description", - "object_id": 357576312, - "column_id": 9 - }, - { - "name": "principal_name", - "object_id": 357576312, - "column_id": 10 - }, - { - "name": "backup_metadata_uuid", - "object_id": 373576369, - "column_id": 1 - }, - { - "name": "database_guid", - "object_id": 373576369, - "column_id": 2 - }, - { - "name": "physical_database_name", - "object_id": 373576369, - "column_id": 3 - }, - { - "name": "time_zone", - "object_id": 373576369, - "column_id": 4 - }, - { - "name": "first_lsn", - "object_id": 373576369, - "column_id": 5 - }, - { - "name": "last_lsn", - "object_id": 373576369, - "column_id": 6 - }, - { - "name": "checkpoint_lsn", - "object_id": 373576369, - "column_id": 7 - }, - { - "name": "database_backup_lsn", - "object_id": 373576369, - "column_id": 8 - }, - { - "name": "backup_start_date", - "object_id": 373576369, - "column_id": 9 - }, - { - "name": "backup_finish_date", - "object_id": 373576369, - "column_id": 10 - }, - { - "name": "backup_type", - "object_id": 373576369, - "column_id": 11 - }, - { - "name": "backup_storage_redundancy", - "object_id": 373576369, - "column_id": 12 - }, - { - "name": "database_version", - "object_id": 373576369, - "column_id": 13 - }, - { - "name": "backup_size", - "object_id": 373576369, - "column_id": 14 - }, - { - "name": "compressed_backup_size", - "object_id": 373576369, - "column_id": 15 - }, - { - "name": "server_name", - "object_id": 373576369, - "column_id": 16 - }, - { - "name": "is_damaged", - "object_id": 373576369, - "column_id": 17 - }, - { - "name": "last_recovery_fork_guid", - "object_id": 373576369, - "column_id": 18 - }, - { - "name": "differential_base_lsn", - "object_id": 373576369, - "column_id": 19 - }, - { - "name": "differential_base_guid", - "object_id": 373576369, - "column_id": 20 - }, - { - "name": "backup_path", - "object_id": 373576369, - "column_id": 21 - }, - { - "name": "last_valid_restore_time", - "object_id": 373576369, - "column_id": 22 - }, - { - "name": "compression_algorithm", - "object_id": 373576369, - "column_id": 23 - }, - { - "name": "logical_server_name", - "object_id": 373576369, - "column_id": 24 - }, - { - "name": "logical_database_name", - "object_id": 373576369, - "column_id": 25 - }, - { - "name": "allocated_db_size_bytes", - "object_id": 373576369, - "column_id": 26 - }, - { - "name": "allocated_data_size_bytes", - "object_id": 373576369, - "column_id": 27 - }, - { - "name": "plan_feedback_id", - "object_id": 389576426, - "column_id": 1 - }, - { - "name": "plan_id", - "object_id": 389576426, - "column_id": 2 - }, - { - "name": "feature_id", - "object_id": 389576426, - "column_id": 3 - }, - { - "name": "feedback_data", - "object_id": 389576426, - "column_id": 4 - }, - { - "name": "state", - "object_id": 389576426, - "column_id": 5 - }, - { - "name": "create_time", - "object_id": 389576426, - "column_id": 6 - }, - { - "name": "last_updated_time", - "object_id": 389576426, - "column_id": 7 - }, - { - "name": "replica_group_id", - "object_id": 389576426, - "column_id": 8 - }, - { - "name": "query_variant_query_id", - "object_id": 405576483, - "column_id": 1 - }, - { - "name": "parent_query_id", - "object_id": 405576483, - "column_id": 2 - }, - { - "name": "dispatcher_plan_id", - "object_id": 405576483, - "column_id": 3 - }, - { - "name": "bucket_id", - "object_id": 565577053, - "column_id": 1 - }, - { - "name": "bucket_data", - "object_id": 565577053, - "column_id": 2 - }, - { - "name": "id", - "object_id": 885578193, - "column_id": 1 - }, - { - "name": "name", - "object_id": 885578193, - "column_id": 2 - }, - { - "name": "start_ip_address", - "object_id": 885578193, - "column_id": 3 - }, - { - "name": "end_ip_address", - "object_id": 885578193, - "column_id": 4 - }, - { - "name": "start_ip_address_value", - "object_id": 885578193, - "column_id": 5 - }, - { - "name": "end_ip_address_value", - "object_id": 885578193, - "column_id": 6 - }, - { - "name": "create_date", - "object_id": 885578193, - "column_id": 7 - }, - { - "name": "modify_date", - "object_id": 885578193, - "column_id": 8 - }, - { - "name": "id", - "object_id": 901578250, - "column_id": 1 - }, - { - "name": "name", - "object_id": 901578250, - "column_id": 2 - }, - { - "name": "start_ip_address", - "object_id": 901578250, - "column_id": 3 - }, - { - "name": "end_ip_address", - "object_id": 901578250, - "column_id": 4 - }, - { - "name": "create_date", - "object_id": 901578250, - "column_id": 5 - }, - { - "name": "modify_date", - "object_id": 901578250, - "column_id": 6 - }, - { - "name": "id", - "object_id": 917578307, - "column_id": 1 - }, - { - "name": "name", - "object_id": 917578307, - "column_id": 2 - }, - { - "name": "start_ipv6_address", - "object_id": 917578307, - "column_id": 3 - }, - { - "name": "end_ipv6_address", - "object_id": 917578307, - "column_id": 4 - }, - { - "name": "start_ipv6_address_msb_value", - "object_id": 917578307, - "column_id": 5 - }, - { - "name": "start_ipv6_address_lsb_value", - "object_id": 917578307, - "column_id": 6 - }, - { - "name": "end_ipv6_address_msb_value", - "object_id": 917578307, - "column_id": 7 - }, - { - "name": "end_ipv6_address_lsb_value", - "object_id": 917578307, - "column_id": 8 - }, - { - "name": "create_date", - "object_id": 917578307, - "column_id": 9 - }, - { - "name": "modify_date", - "object_id": 917578307, - "column_id": 10 - }, - { - "name": "name", - "object_id": 1074102867, - "column_id": 1 - }, - { - "name": "principal_id", - "object_id": 1074102867, - "column_id": 2 - }, - { - "name": "diagram_id", - "object_id": 1074102867, - "column_id": 3 - }, - { - "name": "version", - "object_id": 1074102867, - "column_id": 4 - }, - { - "name": "definition", - "object_id": 1074102867, - "column_id": 5 - }, - { - "name": "schema_name", - "object_id": 1205579333, - "column_id": 1 - }, - { - "name": "table_name", - "object_id": 1205579333, - "column_id": 2 - }, - { - "name": "object_id", - "object_id": 1205579333, - "column_id": 3 - }, - { - "name": "ledger_view_schema_name", - "object_id": 1205579333, - "column_id": 4 - }, - { - "name": "ledger_view_name", - "object_id": 1205579333, - "column_id": 5 - }, - { - "name": "operation_type", - "object_id": 1205579333, - "column_id": 6 - }, - { - "name": "ledger_start_transaction_id", - "object_id": 1205579333, - "column_id": 7 - }, - { - "name": "ledger_end_transaction_id", - "object_id": 1205579333, - "column_id": 8 - }, - { - "name": "ledger_start_sequence_number", - "object_id": 1205579333, - "column_id": 9 - }, - { - "name": "ledger_end_sequence_number", - "object_id": 1205579333, - "column_id": 10 - }, - { - "name": "status", - "object_id": 1205579333, - "column_id": 11 - }, - { - "name": "transaction_id_column_name", - "object_id": 1205579333, - "column_id": 12 - }, - { - "name": "sequence_number_column_name", - "object_id": 1205579333, - "column_id": 13 - }, - { - "name": "operation_type_column_name", - "object_id": 1205579333, - "column_id": 14 - }, - { - "name": "operation_type_desc_column_name", - "object_id": 1205579333, - "column_id": 15 - }, - { - "name": "schema_name", - "object_id": 1221579390, - "column_id": 1 - }, - { - "name": "table_name", - "object_id": 1221579390, - "column_id": 2 - }, - { - "name": "object_id", - "object_id": 1221579390, - "column_id": 3 - }, - { - "name": "ledger_view_schema_name", - "object_id": 1221579390, - "column_id": 4 - }, - { - "name": "ledger_view_name", - "object_id": 1221579390, - "column_id": 5 - }, - { - "name": "operation_type", - "object_id": 1221579390, - "column_id": 6 - }, - { - "name": "ledger_start_transaction_id", - "object_id": 1221579390, - "column_id": 7 - }, - { - "name": "ledger_end_transaction_id", - "object_id": 1221579390, - "column_id": 8 - }, - { - "name": "ledger_start_sequence_number", - "object_id": 1221579390, - "column_id": 9 - }, - { - "name": "ledger_end_sequence_number", - "object_id": 1221579390, - "column_id": 10 - }, - { - "name": "status", - "object_id": 1221579390, - "column_id": 11 - }, - { - "name": "transaction_id_column_name", - "object_id": 1221579390, - "column_id": 12 - }, - { - "name": "sequence_number_column_name", - "object_id": 1221579390, - "column_id": 13 - }, - { - "name": "operation_type_column_name", - "object_id": 1221579390, - "column_id": 14 - }, - { - "name": "operation_type_desc_column_name", - "object_id": 1221579390, - "column_id": 15 - }, - { - "name": "object_id", - "object_id": 1237579447, - "column_id": 1 - }, - { - "name": "column_id", - "object_id": 1237579447, - "column_id": 2 - }, - { - "name": "column_name", - "object_id": 1237579447, - "column_id": 3 - }, - { - "name": "operation_type", - "object_id": 1237579447, - "column_id": 4 - }, - { - "name": "ledger_start_transaction_id", - "object_id": 1237579447, - "column_id": 5 - }, - { - "name": "ledger_end_transaction_id", - "object_id": 1237579447, - "column_id": 6 - }, - { - "name": "ledger_start_sequence_number", - "object_id": 1237579447, - "column_id": 7 - }, - { - "name": "ledger_end_sequence_number", - "object_id": 1237579447, - "column_id": 8 - }, - { - "name": "object_id", - "object_id": 1253579504, - "column_id": 1 - }, - { - "name": "column_id", - "object_id": 1253579504, - "column_id": 2 - }, - { - "name": "column_name", - "object_id": 1253579504, - "column_id": 3 - }, - { - "name": "operation_type", - "object_id": 1253579504, - "column_id": 4 - }, - { - "name": "ledger_start_transaction_id", - "object_id": 1253579504, - "column_id": 5 - }, - { - "name": "ledger_end_transaction_id", - "object_id": 1253579504, - "column_id": 6 - }, - { - "name": "ledger_start_sequence_number", - "object_id": 1253579504, - "column_id": 7 - }, - { - "name": "ledger_end_sequence_number", - "object_id": 1253579504, - "column_id": 8 - }, - { - "name": "storage_type", - "object_id": 1269579561, - "column_id": 1 - }, - { - "name": "path", - "object_id": 1269579561, - "column_id": 2 - }, - { - "name": "last_digest_block_id", - "object_id": 1269579561, - "column_id": 3 - }, - { - "name": "is_current", - "object_id": 1269579561, - "column_id": 4 - }, - { - "name": "replica_group_id", - "object_id": 1285579618, - "column_id": 1 - }, - { - "name": "role_type", - "object_id": 1285579618, - "column_id": 2 - }, - { - "name": "replica_name", - "object_id": 1285579618, - "column_id": 3 - }, - { - "name": "lcid", - "object_id": 1298103665, - "column_id": 1 - }, - { - "name": "diacritics_sensitive", - "object_id": 1298103665, - "column_id": 2 - }, - { - "name": "plan_forcing_location_id", - "object_id": 1301579675, - "column_id": 1 - }, - { - "name": "query_id", - "object_id": 1301579675, - "column_id": 2 - }, - { - "name": "plan_id", - "object_id": 1301579675, - "column_id": 3 - }, - { - "name": "replica_group_id", - "object_id": 1301579675, - "column_id": 4 - }, - { - "name": "timestamp", - "object_id": 1301579675, - "column_id": 5 - }, - { - "name": "plan_forcing_flags", - "object_id": 1301579675, - "column_id": 6 - }, - { - "name": "lcid", - "object_id": 1314103722, - "column_id": 1 - }, - { - "name": "parentid", - "object_id": 1314103722, - "column_id": 2 - }, - { - "name": "term", - "object_id": 1314103722, - "column_id": 3 - }, - { - "name": "stateid", - "object_id": 1314103722, - "column_id": 4 - }, - { - "name": "phraseid", - "object_id": 1314103722, - "column_id": 5 - }, - { - "name": "runtime_stats_id", - "object_id": 1317579732, - "column_id": 1 - }, - { - "name": "plan_id", - "object_id": 1317579732, - "column_id": 2 - }, - { - "name": "runtime_stats_interval_id", - "object_id": 1317579732, - "column_id": 3 - }, - { - "name": "execution_type", - "object_id": 1317579732, - "column_id": 4 - }, - { - "name": "first_execution_time", - "object_id": 1317579732, - "column_id": 5 - }, - { - "name": "last_execution_time", - "object_id": 1317579732, - "column_id": 6 - }, - { - "name": "count_executions", - "object_id": 1317579732, - "column_id": 7 - }, - { - "name": "total_duration", - "object_id": 1317579732, - "column_id": 8 - }, - { - "name": "last_duration", - "object_id": 1317579732, - "column_id": 9 - }, - { - "name": "min_duration", - "object_id": 1317579732, - "column_id": 10 - }, - { - "name": "max_duration", - "object_id": 1317579732, - "column_id": 11 - }, - { - "name": "sumsquare_duration", - "object_id": 1317579732, - "column_id": 12 - }, - { - "name": "total_cpu_time", - "object_id": 1317579732, - "column_id": 13 - }, - { - "name": "last_cpu_time", - "object_id": 1317579732, - "column_id": 14 - }, - { - "name": "min_cpu_time", - "object_id": 1317579732, - "column_id": 15 - }, - { - "name": "max_cpu_time", - "object_id": 1317579732, - "column_id": 16 - }, - { - "name": "sumsquare_cpu_time", - "object_id": 1317579732, - "column_id": 17 - }, - { - "name": "total_logical_io_reads", - "object_id": 1317579732, - "column_id": 18 - }, - { - "name": "last_logical_io_reads", - "object_id": 1317579732, - "column_id": 19 - }, - { - "name": "min_logical_io_reads", - "object_id": 1317579732, - "column_id": 20 - }, - { - "name": "max_logical_io_reads", - "object_id": 1317579732, - "column_id": 21 - }, - { - "name": "sumsquare_logical_io_reads", - "object_id": 1317579732, - "column_id": 22 - }, - { - "name": "total_logical_io_writes", - "object_id": 1317579732, - "column_id": 23 - }, - { - "name": "last_logical_io_writes", - "object_id": 1317579732, - "column_id": 24 - }, - { - "name": "min_logical_io_writes", - "object_id": 1317579732, - "column_id": 25 - }, - { - "name": "max_logical_io_writes", - "object_id": 1317579732, - "column_id": 26 - }, - { - "name": "sumsquare_logical_io_writes", - "object_id": 1317579732, - "column_id": 27 - }, - { - "name": "total_physical_io_reads", - "object_id": 1317579732, - "column_id": 28 - }, - { - "name": "last_physical_io_reads", - "object_id": 1317579732, - "column_id": 29 - }, - { - "name": "min_physical_io_reads", - "object_id": 1317579732, - "column_id": 30 - }, - { - "name": "max_physical_io_reads", - "object_id": 1317579732, - "column_id": 31 - }, - { - "name": "sumsquare_physical_io_reads", - "object_id": 1317579732, - "column_id": 32 - }, - { - "name": "total_clr_time", - "object_id": 1317579732, - "column_id": 33 - }, - { - "name": "last_clr_time", - "object_id": 1317579732, - "column_id": 34 - }, - { - "name": "min_clr_time", - "object_id": 1317579732, - "column_id": 35 - }, - { - "name": "max_clr_time", - "object_id": 1317579732, - "column_id": 36 - }, - { - "name": "sumsquare_clr_time", - "object_id": 1317579732, - "column_id": 37 - }, - { - "name": "total_dop", - "object_id": 1317579732, - "column_id": 38 - }, - { - "name": "last_dop", - "object_id": 1317579732, - "column_id": 39 - }, - { - "name": "min_dop", - "object_id": 1317579732, - "column_id": 40 - }, - { - "name": "max_dop", - "object_id": 1317579732, - "column_id": 41 - }, - { - "name": "sumsquare_dop", - "object_id": 1317579732, - "column_id": 42 - }, - { - "name": "total_query_max_used_memory", - "object_id": 1317579732, - "column_id": 43 - }, - { - "name": "last_query_max_used_memory", - "object_id": 1317579732, - "column_id": 44 - }, - { - "name": "min_query_max_used_memory", - "object_id": 1317579732, - "column_id": 45 - }, - { - "name": "max_query_max_used_memory", - "object_id": 1317579732, - "column_id": 46 - }, - { - "name": "sumsquare_query_max_used_memory", - "object_id": 1317579732, - "column_id": 47 - }, - { - "name": "total_rowcount", - "object_id": 1317579732, - "column_id": 48 - }, - { - "name": "last_rowcount", - "object_id": 1317579732, - "column_id": 49 - }, - { - "name": "min_rowcount", - "object_id": 1317579732, - "column_id": 50 - }, - { - "name": "max_rowcount", - "object_id": 1317579732, - "column_id": 51 - }, - { - "name": "sumsquare_rowcount", - "object_id": 1317579732, - "column_id": 52 - }, - { - "name": "total_num_physical_io_reads", - "object_id": 1317579732, - "column_id": 53 - }, - { - "name": "last_num_physical_io_reads", - "object_id": 1317579732, - "column_id": 54 - }, - { - "name": "min_num_physical_io_reads", - "object_id": 1317579732, - "column_id": 55 - }, - { - "name": "max_num_physical_io_reads", - "object_id": 1317579732, - "column_id": 56 - }, - { - "name": "sumsquare_num_physical_io_reads", - "object_id": 1317579732, - "column_id": 57 - }, - { - "name": "total_log_bytes_used", - "object_id": 1317579732, - "column_id": 58 - }, - { - "name": "last_log_bytes_used", - "object_id": 1317579732, - "column_id": 59 - }, - { - "name": "min_log_bytes_used", - "object_id": 1317579732, - "column_id": 60 - }, - { - "name": "max_log_bytes_used", - "object_id": 1317579732, - "column_id": 61 - }, - { - "name": "sumsquare_log_bytes_used", - "object_id": 1317579732, - "column_id": 62 - }, - { - "name": "total_tempdb_space_used", - "object_id": 1317579732, - "column_id": 63 - }, - { - "name": "last_tempdb_space_used", - "object_id": 1317579732, - "column_id": 64 - }, - { - "name": "min_tempdb_space_used", - "object_id": 1317579732, - "column_id": 65 - }, - { - "name": "max_tempdb_space_used", - "object_id": 1317579732, - "column_id": 66 - }, - { - "name": "sumsquare_tempdb_space_used", - "object_id": 1317579732, - "column_id": 67 - }, - { - "name": "total_page_server_io_reads", - "object_id": 1317579732, - "column_id": 68 - }, - { - "name": "last_page_server_io_reads", - "object_id": 1317579732, - "column_id": 69 - }, - { - "name": "min_page_server_io_reads", - "object_id": 1317579732, - "column_id": 70 - }, - { - "name": "max_page_server_io_reads", - "object_id": 1317579732, - "column_id": 71 - }, - { - "name": "sumsquare_page_server_io_reads", - "object_id": 1317579732, - "column_id": 72 - }, - { - "name": "replica_group_id", - "object_id": 1317579732, - "column_id": 73 - }, - { - "name": "phraseid", - "object_id": 1330103779, - "column_id": 1 - }, - { - "name": "lcid", - "object_id": 1330103779, - "column_id": 2 - }, - { - "name": "groupid", - "object_id": 1330103779, - "column_id": 3 - }, - { - "name": "isExpansion", - "object_id": 1330103779, - "column_id": 4 - }, - { - "name": "isLHSOfReplacement", - "object_id": 1330103779, - "column_id": 5 - }, - { - "name": "terms", - "object_id": 1330103779, - "column_id": 6 - }, - { - "name": "wait_stats_id", - "object_id": 1333579789, - "column_id": 1 - }, - { - "name": "runtime_stats_interval_id", - "object_id": 1333579789, - "column_id": 2 - }, - { - "name": "plan_id", - "object_id": 1333579789, - "column_id": 3 - }, - { - "name": "wait_category", - "object_id": 1333579789, - "column_id": 4 - }, - { - "name": "execution_type", - "object_id": 1333579789, - "column_id": 5 - }, - { - "name": "count_executions", - "object_id": 1333579789, - "column_id": 6 - }, - { - "name": "total_query_wait_time_ms", - "object_id": 1333579789, - "column_id": 7 - }, - { - "name": "last_query_wait_time_ms", - "object_id": 1333579789, - "column_id": 8 - }, - { - "name": "min_query_wait_time_ms", - "object_id": 1333579789, - "column_id": 9 - }, - { - "name": "max_query_wait_time_ms", - "object_id": 1333579789, - "column_id": 10 - }, - { - "name": "sumsquare_query_wait_time_ms", - "object_id": 1333579789, - "column_id": 11 - }, - { - "name": "replica_group_id", - "object_id": 1333579789, - "column_id": 12 - }, - { - "name": "time_snapshot", - "object_id": 1381579960, - "column_id": 1 - }, - { - "name": "event_type", - "object_id": 1381579960, - "column_id": 2 - }, - { - "name": "ParentProductCategoryName", - "object_id": 1541580530, - "column_id": 1 - }, - { - "name": "ProductCategoryName", - "object_id": 1541580530, - "column_id": 2 - }, - { - "name": "ProductCategoryID", - "object_id": 1541580530, - "column_id": 3 - }, - { - "name": "CustomerID", - "object_id": 1573580644, - "column_id": 1 - }, - { - "name": "NameStyle", - "object_id": 1573580644, - "column_id": 2 - }, - { - "name": "Title", - "object_id": 1573580644, - "column_id": 3 - }, - { - "name": "FirstName", - "object_id": 1573580644, - "column_id": 4 - }, - { - "name": "MiddleName", - "object_id": 1573580644, - "column_id": 5 - }, - { - "name": "LastName", - "object_id": 1573580644, - "column_id": 6 - }, - { - "name": "Suffix", - "object_id": 1573580644, - "column_id": 7 - }, - { - "name": "CompanyName", - "object_id": 1573580644, - "column_id": 8 - }, - { - "name": "SalesPerson", - "object_id": 1573580644, - "column_id": 9 - }, - { - "name": "EmailAddress", - "object_id": 1573580644, - "column_id": 10 - }, - { - "name": "Phone", - "object_id": 1573580644, - "column_id": 11 - }, - { - "name": "PasswordHash", - "object_id": 1573580644, - "column_id": 12 - }, - { - "name": "PasswordSalt", - "object_id": 1573580644, - "column_id": 13 - }, - { - "name": "rowguid", - "object_id": 1573580644, - "column_id": 14 - }, - { - "name": "ModifiedDate", - "object_id": 1573580644, - "column_id": 15 - }, - { - "name": "CustomerID", - "object_id": 1605580758, - "column_id": 1 - }, - { - "name": "FirstName", - "object_id": 1605580758, - "column_id": 2 - }, - { - "name": "LastName", - "object_id": 1605580758, - "column_id": 3 - }, - { - "name": "ProductModelID", - "object_id": 1621580815, - "column_id": 1 - }, - { - "name": "Name", - "object_id": 1621580815, - "column_id": 2 - }, - { - "name": "CatalogDescription", - "object_id": 1621580815, - "column_id": 3 - }, - { - "name": "rowguid", - "object_id": 1621580815, - "column_id": 4 - }, - { - "name": "ModifiedDate", - "object_id": 1621580815, - "column_id": 5 - }, - { - "name": "ProductModelID", - "object_id": 1653580929, - "column_id": 1 - }, - { - "name": "Name", - "object_id": 1653580929, - "column_id": 2 - }, - { - "name": "Summary", - "object_id": 1653580929, - "column_id": 3 - }, - { - "name": "Manufacturer", - "object_id": 1653580929, - "column_id": 4 - }, - { - "name": "Copyright", - "object_id": 1653580929, - "column_id": 5 - }, - { - "name": "ProductURL", - "object_id": 1653580929, - "column_id": 6 - }, - { - "name": "WarrantyPeriod", - "object_id": 1653580929, - "column_id": 7 - }, - { - "name": "WarrantyDescription", - "object_id": 1653580929, - "column_id": 8 - }, - { - "name": "NoOfYears", - "object_id": 1653580929, - "column_id": 9 - }, - { - "name": "MaintenanceDescription", - "object_id": 1653580929, - "column_id": 10 - }, - { - "name": "Wheel", - "object_id": 1653580929, - "column_id": 11 - }, - { - "name": "Saddle", - "object_id": 1653580929, - "column_id": 12 - }, - { - "name": "Pedal", - "object_id": 1653580929, - "column_id": 13 - }, - { - "name": "BikeFrame", - "object_id": 1653580929, - "column_id": 14 - }, - { - "name": "Crankset", - "object_id": 1653580929, - "column_id": 15 - }, - { - "name": "PictureAngle", - "object_id": 1653580929, - "column_id": 16 - }, - { - "name": "PictureSize", - "object_id": 1653580929, - "column_id": 17 - }, - { - "name": "ProductPhotoID", - "object_id": 1653580929, - "column_id": 18 - }, - { - "name": "Material", - "object_id": 1653580929, - "column_id": 19 - }, - { - "name": "Color", - "object_id": 1653580929, - "column_id": 20 - }, - { - "name": "ProductLine", - "object_id": 1653580929, - "column_id": 21 - }, - { - "name": "Style", - "object_id": 1653580929, - "column_id": 22 - }, - { - "name": "RiderExperience", - "object_id": 1653580929, - "column_id": 23 - }, - { - "name": "rowguid", - "object_id": 1653580929, - "column_id": 24 - }, - { - "name": "ModifiedDate", - "object_id": 1653580929, - "column_id": 25 - }, - { - "name": "ProductDescriptionID", - "object_id": 1669580986, - "column_id": 1 - }, - { - "name": "Description", - "object_id": 1669580986, - "column_id": 2 - }, - { - "name": "rowguid", - "object_id": 1669580986, - "column_id": 3 - }, - { - "name": "ModifiedDate", - "object_id": 1669580986, - "column_id": 4 - }, - { - "name": "ProductID", - "object_id": 1701581100, - "column_id": 1 - }, - { - "name": "Name", - "object_id": 1701581100, - "column_id": 2 - }, - { - "name": "ProductNumber", - "object_id": 1701581100, - "column_id": 3 - }, - { - "name": "Color", - "object_id": 1701581100, - "column_id": 4 - }, - { - "name": "StandardCost", - "object_id": 1701581100, - "column_id": 5 - }, - { - "name": "ListPrice", - "object_id": 1701581100, - "column_id": 6 - }, - { - "name": "Size", - "object_id": 1701581100, - "column_id": 7 - }, - { - "name": "Weight", - "object_id": 1701581100, - "column_id": 8 - }, - { - "name": "ProductCategoryID", - "object_id": 1701581100, - "column_id": 9 - }, - { - "name": "ProductModelID", - "object_id": 1701581100, - "column_id": 10 - }, - { - "name": "SellStartDate", - "object_id": 1701581100, - "column_id": 11 - }, - { - "name": "SellEndDate", - "object_id": 1701581100, - "column_id": 12 - }, - { - "name": "DiscontinuedDate", - "object_id": 1701581100, - "column_id": 13 - }, - { - "name": "ThumbNailPhoto", - "object_id": 1701581100, - "column_id": 14 - }, - { - "name": "ThumbnailPhotoFileName", - "object_id": 1701581100, - "column_id": 15 - }, - { - "name": "rowguid", - "object_id": 1701581100, - "column_id": 16 - }, - { - "name": "ModifiedDate", - "object_id": 1701581100, - "column_id": 17 - }, - { - "name": "ProductModelID", - "object_id": 1733581214, - "column_id": 1 - }, - { - "name": "ProductDescriptionID", - "object_id": 1733581214, - "column_id": 2 - }, - { - "name": "Culture", - "object_id": 1733581214, - "column_id": 3 - }, - { - "name": "rowguid", - "object_id": 1733581214, - "column_id": 4 - }, - { - "name": "ModifiedDate", - "object_id": 1733581214, - "column_id": 5 - }, - { - "name": "ProductID", - "object_id": 1765581328, - "column_id": 1 - }, - { - "name": "Name", - "object_id": 1765581328, - "column_id": 2 - }, - { - "name": "ProductModel", - "object_id": 1765581328, - "column_id": 3 - }, - { - "name": "Culture", - "object_id": 1765581328, - "column_id": 4 - }, - { - "name": "Description", - "object_id": 1765581328, - "column_id": 5 - }, - { - "name": "ProductCategoryID", - "object_id": 1781581385, - "column_id": 1 - }, - { - "name": "ParentProductCategoryID", - "object_id": 1781581385, - "column_id": 2 - }, - { - "name": "Name", - "object_id": 1781581385, - "column_id": 3 - }, - { - "name": "rowguid", - "object_id": 1781581385, - "column_id": 4 - }, - { - "name": "ModifiedDate", - "object_id": 1781581385, - "column_id": 5 - }, - { - "name": "ParentProductCategoryName", - "object_id": 1813581499, - "column_id": 1 - }, - { - "name": "ProductCategoryName", - "object_id": 1813581499, - "column_id": 2 - }, - { - "name": "ProductCategoryID", - "object_id": 1813581499, - "column_id": 3 - }, - { - "name": "SystemInformationID", - "object_id": 1829581556, - "column_id": 1 - }, - { - "name": "Database Version", - "object_id": 1829581556, - "column_id": 2 - }, - { - "name": "VersionDate", - "object_id": 1829581556, - "column_id": 3 - }, - { - "name": "ModifiedDate", - "object_id": 1829581556, - "column_id": 4 - }, - { - "name": "ErrorLogID", - "object_id": 1861581670, - "column_id": 1 - }, - { - "name": "ErrorTime", - "object_id": 1861581670, - "column_id": 2 - }, - { - "name": "UserName", - "object_id": 1861581670, - "column_id": 3 - }, - { - "name": "ErrorNumber", - "object_id": 1861581670, - "column_id": 4 - }, - { - "name": "ErrorSeverity", - "object_id": 1861581670, - "column_id": 5 - }, - { - "name": "ErrorState", - "object_id": 1861581670, - "column_id": 6 - }, - { - "name": "ErrorProcedure", - "object_id": 1861581670, - "column_id": 7 - }, - { - "name": "ErrorLine", - "object_id": 1861581670, - "column_id": 8 - }, - { - "name": "ErrorMessage", - "object_id": 1861581670, - "column_id": 9 - }, - { - "name": "AddressID", - "object_id": 1893581784, - "column_id": 1 - }, - { - "name": "AddressLine1", - "object_id": 1893581784, - "column_id": 2 - }, - { - "name": "AddressLine2", - "object_id": 1893581784, - "column_id": 3 - }, - { - "name": "City", - "object_id": 1893581784, - "column_id": 4 - }, - { - "name": "StateProvince", - "object_id": 1893581784, - "column_id": 5 - }, - { - "name": "CountryRegion", - "object_id": 1893581784, - "column_id": 6 - }, - { - "name": "PostalCode", - "object_id": 1893581784, - "column_id": 7 - }, - { - "name": "rowguid", - "object_id": 1893581784, - "column_id": 8 - }, - { - "name": "ModifiedDate", - "object_id": 1893581784, - "column_id": 9 - }, - { - "name": "CustomerID", - "object_id": 1925581898, - "column_id": 1 - }, - { - "name": "AddressID", - "object_id": 1925581898, - "column_id": 2 - }, - { - "name": "AddressType", - "object_id": 1925581898, - "column_id": 3 - }, - { - "name": "rowguid", - "object_id": 1925581898, - "column_id": 4 - }, - { - "name": "ModifiedDate", - "object_id": 1925581898, - "column_id": 5 - }, - { - "name": "SalesOrderID", - "object_id": 1957582012, - "column_id": 1 - }, - { - "name": "SalesOrderDetailID", - "object_id": 1957582012, - "column_id": 2 - }, - { - "name": "OrderQty", - "object_id": 1957582012, - "column_id": 3 - }, - { - "name": "ProductID", - "object_id": 1957582012, - "column_id": 4 - }, - { - "name": "UnitPrice", - "object_id": 1957582012, - "column_id": 5 - }, - { - "name": "UnitPriceDiscount", - "object_id": 1957582012, - "column_id": 6 - }, - { - "name": "LineTotal", - "object_id": 1957582012, - "column_id": 7 - }, - { - "name": "rowguid", - "object_id": 1957582012, - "column_id": 8 - }, - { - "name": "ModifiedDate", - "object_id": 1957582012, - "column_id": 9 - }, - { - "name": "SalesOrderID", - "object_id": 1989582126, - "column_id": 1 - }, - { - "name": "RevisionNumber", - "object_id": 1989582126, - "column_id": 2 - }, - { - "name": "OrderDate", - "object_id": 1989582126, - "column_id": 3 - }, - { - "name": "DueDate", - "object_id": 1989582126, - "column_id": 4 - }, - { - "name": "ShipDate", - "object_id": 1989582126, - "column_id": 5 - }, - { - "name": "Status", - "object_id": 1989582126, - "column_id": 6 - }, - { - "name": "OnlineOrderFlag", - "object_id": 1989582126, - "column_id": 7 - }, - { - "name": "SalesOrderNumber", - "object_id": 1989582126, - "column_id": 8 - }, - { - "name": "PurchaseOrderNumber", - "object_id": 1989582126, - "column_id": 9 - }, - { - "name": "AccountNumber", - "object_id": 1989582126, - "column_id": 10 - }, - { - "name": "CustomerID", - "object_id": 1989582126, - "column_id": 11 - }, - { - "name": "ShipToAddressID", - "object_id": 1989582126, - "column_id": 12 - }, - { - "name": "BillToAddressID", - "object_id": 1989582126, - "column_id": 13 - }, - { - "name": "ShipMethod", - "object_id": 1989582126, - "column_id": 14 - }, - { - "name": "CreditCardApprovalCode", - "object_id": 1989582126, - "column_id": 15 - }, - { - "name": "SubTotal", - "object_id": 1989582126, - "column_id": 16 - }, - { - "name": "TaxAmt", - "object_id": 1989582126, - "column_id": 17 - }, - { - "name": "Freight", - "object_id": 1989582126, - "column_id": 18 - }, - { - "name": "TotalDue", - "object_id": 1989582126, - "column_id": 19 - }, - { - "name": "Comment", - "object_id": 1989582126, - "column_id": 20 - }, - { - "name": "rowguid", - "object_id": 1989582126, - "column_id": 21 - }, - { - "name": "ModifiedDate", - "object_id": 1989582126, - "column_id": 22 - }, - { - "name": "status", - "object_id": 1993058136, - "column_id": 1 - }, - { - "name": "priority", - "object_id": 1993058136, - "column_id": 2 - }, - { - "name": "queuing_order", - "object_id": 1993058136, - "column_id": 3 - }, - { - "name": "conversation_group_id", - "object_id": 1993058136, - "column_id": 4 - }, - { - "name": "conversation_handle", - "object_id": 1993058136, - "column_id": 5 - }, - { - "name": "message_sequence_number", - "object_id": 1993058136, - "column_id": 6 - }, - { - "name": "message_id", - "object_id": 1993058136, - "column_id": 7 - }, - { - "name": "message_type_id", - "object_id": 1993058136, - "column_id": 8 - }, - { - "name": "service_id", - "object_id": 1993058136, - "column_id": 9 - }, - { - "name": "service_contract_id", - "object_id": 1993058136, - "column_id": 10 - }, - { - "name": "validation", - "object_id": 1993058136, - "column_id": 11 - }, - { - "name": "next_fragment", - "object_id": 1993058136, - "column_id": 12 - }, - { - "name": "fragment_size", - "object_id": 1993058136, - "column_id": 13 - }, - { - "name": "fragment_bitmap", - "object_id": 1993058136, - "column_id": 14 - }, - { - "name": "binary_message_body", - "object_id": 1993058136, - "column_id": 15 - }, - { - "name": "message_enqueue_time", - "object_id": 1993058136, - "column_id": 16 - }, - { - "name": "status", - "object_id": 2025058250, - "column_id": 1 - }, - { - "name": "priority", - "object_id": 2025058250, - "column_id": 2 - }, - { - "name": "queuing_order", - "object_id": 2025058250, - "column_id": 3 - }, - { - "name": "conversation_group_id", - "object_id": 2025058250, - "column_id": 4 - }, - { - "name": "conversation_handle", - "object_id": 2025058250, - "column_id": 5 - }, - { - "name": "message_sequence_number", - "object_id": 2025058250, - "column_id": 6 - }, - { - "name": "message_id", - "object_id": 2025058250, - "column_id": 7 - }, - { - "name": "message_type_id", - "object_id": 2025058250, - "column_id": 8 - }, - { - "name": "service_id", - "object_id": 2025058250, - "column_id": 9 - }, - { - "name": "service_contract_id", - "object_id": 2025058250, - "column_id": 10 - }, - { - "name": "validation", - "object_id": 2025058250, - "column_id": 11 - }, - { - "name": "next_fragment", - "object_id": 2025058250, - "column_id": 12 - }, - { - "name": "fragment_size", - "object_id": 2025058250, - "column_id": 13 - }, - { - "name": "fragment_bitmap", - "object_id": 2025058250, - "column_id": 14 - }, - { - "name": "binary_message_body", - "object_id": 2025058250, - "column_id": 15 - }, - { - "name": "message_enqueue_time", - "object_id": 2025058250, - "column_id": 16 - }, - { - "name": "status", - "object_id": 2057058364, - "column_id": 1 - }, - { - "name": "priority", - "object_id": 2057058364, - "column_id": 2 - }, - { - "name": "queuing_order", - "object_id": 2057058364, - "column_id": 3 - }, - { - "name": "conversation_group_id", - "object_id": 2057058364, - "column_id": 4 - }, - { - "name": "conversation_handle", - "object_id": 2057058364, - "column_id": 5 - }, - { - "name": "message_sequence_number", - "object_id": 2057058364, - "column_id": 6 - }, - { - "name": "message_id", - "object_id": 2057058364, - "column_id": 7 - }, - { - "name": "message_type_id", - "object_id": 2057058364, - "column_id": 8 - }, - { - "name": "service_id", - "object_id": 2057058364, - "column_id": 9 - }, - { - "name": "service_contract_id", - "object_id": 2057058364, - "column_id": 10 - }, - { - "name": "validation", - "object_id": 2057058364, - "column_id": 11 - }, - { - "name": "next_fragment", - "object_id": 2057058364, - "column_id": 12 - }, - { - "name": "fragment_size", - "object_id": 2057058364, - "column_id": 13 - }, - { - "name": "fragment_bitmap", - "object_id": 2057058364, - "column_id": 14 - }, - { - "name": "binary_message_body", - "object_id": 2057058364, - "column_id": 15 - }, - { - "name": "message_enqueue_time", - "object_id": 2057058364, - "column_id": 16 - }, - { - "name": "oplsn_fseqno", - "object_id": 2073058421, - "column_id": 1 - }, - { - "name": "oplsn_bOffset", - "object_id": 2073058421, - "column_id": 2 - }, - { - "name": "oplsn_slotid", - "object_id": 2073058421, - "column_id": 3 - }, - { - "name": "file_id", - "object_id": 2073058421, - "column_id": 4 - }, - { - "name": "rowset_guid", - "object_id": 2073058421, - "column_id": 5 - }, - { - "name": "column_guid", - "object_id": 2073058421, - "column_id": 6 - }, - { - "name": "filestream_value_name", - "object_id": 2073058421, - "column_id": 7 - }, - { - "name": "transaction_sequence_num", - "object_id": 2073058421, - "column_id": 8 - }, - { - "name": "status", - "object_id": 2073058421, - "column_id": 9 - }, - { - "name": "size", - "object_id": 2073058421, - "column_id": 10 - }, - { - "name": "commit_ts", - "object_id": 2089058478, - "column_id": 1 - }, - { - "name": "xdes_id", - "object_id": 2089058478, - "column_id": 2 - }, - { - "name": "commit_lbn", - "object_id": 2089058478, - "column_id": 3 - }, - { - "name": "commit_csn", - "object_id": 2089058478, - "column_id": 4 - }, - { - "name": "commit_time", - "object_id": 2089058478, - "column_id": 5 - }, - { - "name": "dbfragid", - "object_id": 2089058478, - "column_id": 6 - }, - { - "name": "table_id", - "object_id": 2105058535, - "column_id": 1 - }, - { - "name": "oplsn_fseqno", - "object_id": 2105058535, - "column_id": 2 - }, - { - "name": "oplsn_bOffset", - "object_id": 2105058535, - "column_id": 3 - }, - { - "name": "oplsn_slotid", - "object_id": 2105058535, - "column_id": 4 - }, - { - "name": "item_guid", - "object_id": 2105058535, - "column_id": 5 + "FK_Name": "FK_Product_ProductModel_ProductModelID", + "Parent_Table": "[SalesLT].[Product]", + "Parent_Column": "ProductModelID", + "Referenced_Table": "[SalesLT].[ProductModel]", + "Referenced_Column": "ProductModelID" + }, + { + "FK_Name": "FK_Product_ProductCategory_ProductCategoryID", + "Parent_Table": "[SalesLT].[Product]", + "Parent_Column": "ProductCategoryID", + "Referenced_Table": "[SalesLT].[ProductCategory]", + "Referenced_Column": "ProductCategoryID" } ] }, diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD GetTables.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD GetTables.json index ca4d0fb329..dad12db94a 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD GetTables.json +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD GetTables.json @@ -1,5 +1,5 @@ { - "@odata.context": "https://zendesk-dfscus.azconn-dfscus-002.p.azurewebsites.net/$metadata#datasets('default')/tables", + "@odata.context": "https://zendesk-dfwus.azconn-dfwus-001.p.azurewebsites.net/$metadata#datasets('default')/tables", "value": [ { "Name": "activities", diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetRows.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetRows.json new file mode 100644 index 0000000000..eded034927 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetRows.json @@ -0,0 +1,55 @@ +{ + "@odata.context":"https://4d4a8e81-17a4-4a92-9bfe-8d12e607fb7f.08.common.tip1.azure-apihub.net/apim/zendesk/ca06d34f4b684e38b7cf4c0f517a7e99/$metadata#datasets('default')/tables('tickets')/items","value":[ + { + "@odata.etag":"","ItemInternalId":"f41edfa3-1246-430c-b165-c7fe223d04ae","id":24,"url":"https://startup8613.zendesk.com/api/v2/tickets/24.json","subject":"SAMPLE TICKET: Gift card","raw_subject":"SAMPLE TICKET: Gift card","description":"Hi there, I have a friend who recently moved overseas and I was thinking of sending her a housewarming gift. I saw that you offer international gift cards, but I\u2019m a little unsure about how the whole process works.\n\nCould you explain? Like what the denominations are, how we determine the recipient\u2019s currency, and how to personalize the gift card?\n\nCheers,\nBlake Jackson","priority":"normal","status":"open","requester_id":33720018216979,"submitter_id":33720018216979,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:33Z","updated_at":"2024-09-24T01:34:33Z","key":"24" + },{ + "@odata.etag":"","ItemInternalId":"207d5fd0-5d44-47cc-ac86-27488ce83a04","id":25,"url":"https://startup8613.zendesk.com/api/v2/tickets/25.json","subject":"SAMPLE TICKET: Gift card expiring","raw_subject":"SAMPLE TICKET: Gift card expiring","description":"Hey there, I was lucky enough to receive a gift card from a friend as a housewarming gift. Small problem, I\u2019ve been so swamped with the move I totally forgot about it until now and it expires in a week!\n\nCan you extend the expiration date?\n\nHelp,\nLuka Jensen","priority":"normal","status":"open","requester_id":33720000283795,"submitter_id":33720000283795,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:33Z","updated_at":"2024-09-24T01:34:33Z","key":"25" + },{ + "@odata.etag":"","ItemInternalId":"b6d12103-9595-4028-b45d-8b1cd02dcc1b","id":23,"url":"https://startup8613.zendesk.com/api/v2/tickets/23.json","subject":"SAMPLE TICKET: Does this impact my credit?","raw_subject":"SAMPLE TICKET: Does this impact my credit?","description":"Hey hey, I\u2019m totally loving your Minimalist Elegance collection! But before I fill up my cart, I\u2019ve got some questions about your payment plans.\n\nI\u2019m curious about how choosing them might be beneficial and will this impact my credit score? Could you break down the advantages of both the Homebuy Layaway and Installment Plan for me specifically in terms of their benefits related to price and instant ownership?\n\nLastly, I wanted to know whether the payment plan I choose might impact the delivery time slot of my chosen pieces.\n\nAppreciate your help!","priority":"normal","status":"open","requester_id":33719972942611,"submitter_id":33719972942611,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:32Z","updated_at":"2024-09-24T01:34:32Z","key":"23" + },{ + "@odata.etag":"","ItemInternalId":"9d91baf9-b20c-47f9-906d-d442c8d11f43","id":19,"url":"https://startup8613.zendesk.com/api/v2/tickets/19.json","subject":"SAMPLE TICKET: Do I put it together?","raw_subject":"SAMPLE TICKET: Do I put it together?","description":"Hey there, I\u2019ve been browsing your site and I keep seeing this term \"Flat Pack Delivery\".\n\nI\u2019m not sure what this really means. Does it shorten my waiting time for delivery or does it just make the package smaller?\n\nAnd once the flat packed item arrives, does this mean I have to put it together?\n\nYour help in clarifying this would be much appreciated!\n\nThanks,\nSoobin Do","priority":"normal","status":"open","requester_id":33720000167699,"submitter_id":33720000167699,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:30Z","updated_at":"2024-09-24T01:34:30Z","key":"19" + },{ + "@odata.etag":"","ItemInternalId":"84c715f4-f310-4b57-b81c-54b130e5f704","id":16,"url":"https://startup8613.zendesk.com/api/v2/tickets/16.json","subject":"SAMPLE TICKET: Where\u2019s my order?","raw_subject":"SAMPLE TICKET: Where\u2019s my order?","description":"Hiya, I\u2019ve recently splurged on some really cool furniture (first time, yay). I got an email confirming all the details with an order number, but here\u2019s the hiccup \u2026 I\u2019m completely lost about how to see my order status.\n\nIs there a way to check this? Or can you do it for me?\nThanks a bunch,\nElla Rivera","priority":"normal","status":"open","requester_id":33719955916179,"submitter_id":33719955916179,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:29Z","updated_at":"2024-09-24T01:34:29Z","key":"16" + },{ + "@odata.etag":"","ItemInternalId":"43abf833-e563-4132-bb79-718525f8ebec","id":11,"url":"https://startup8613.zendesk.com/api/v2/tickets/11.json","subject":"SAMPLE TICKET: Do you do gift wrapping?","raw_subject":"SAMPLE TICKET: Do you do gift wrapping?","description":"Hello! Does your store do gift wrapping? I want to order a dinner set for a friend and it would be great if I could just send it straight to her, already gift wrapped.","priority":"normal","status":"open","requester_id":33719972942611,"submitter_id":33719972942611,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:26Z","updated_at":"2024-09-24T01:35:45Z","key":"11" + },{ + "@odata.etag":"","ItemInternalId":"1a2ed272-c63f-4139-b928-818ccb3225c7","id":8,"url":"https://startup8613.zendesk.com/api/v2/tickets/8.json","subject":"SAMPLE TICKET: Ordered the wrong color","raw_subject":"SAMPLE TICKET: Ordered the wrong color","description":"Hi! I accidentally ordered my item in the wrong color, is it possible to change my order?","priority":"urgent","status":"open","requester_id":33719972845331,"submitter_id":33719972845331,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:24Z","updated_at":"2024-09-24T01:37:06Z","key":"8" + },{ + "@odata.etag":"","ItemInternalId":"dd0dd780-710e-434f-b476-7abf778ea5a2","id":21,"url":"https://startup8613.zendesk.com/api/v2/tickets/21.json","subject":"SAMPLE TICKET: Exchange my order","raw_subject":"SAMPLE TICKET: Exchange my order","description":"I want to swap my purchase for store credit. I recently ordered a piece from y\u2019all, but it doesn\u2019t quite gel with my living room aesthetics like I thought it would.\n\nI see that there\u2019s an option to exchange my item for store credit. Can you shed some light on how this works? Do I have to return the item in its original packaging? Will I get full credit?\n\nAlso, could you briefly guide me on initiating the exchange process through your website?\n\nBest\nMarcus Allen","priority":"normal","status":"open","requester_id":33720000222995,"submitter_id":33720000222995,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:31Z","updated_at":"2024-09-24T01:34:31Z","key":"21" + },{ + "@odata.etag":"","ItemInternalId":"c0018f2c-628f-4a4b-b8ae-9765eb3e0c8a","id":20,"url":"https://startup8613.zendesk.com/api/v2/tickets/20.json","subject":"SAMPLE TICKET: Return my order","raw_subject":"SAMPLE TICKET: Return my order","description":"Hey Homebuy team, I recently ordered a piece of furniture which, unfortunately, doesn\u2019t quite fit the way I hoped it would in my home.\n\nHow do I go about returning it? Is there a time frame within which I need to make the return?\n\nAlso, how will my refund be calculated? I remember reading something about it depending on whether or not the item has been assembled, but I\u2019m not certain.\n\nHoping I can still return this \u2026 thanks.\n\nRam Sitwat","priority":"normal","status":"open","requester_id":33719972845331,"submitter_id":33719972845331,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:30Z","updated_at":"2024-09-24T01:34:30Z","key":"20" + },{ + "@odata.etag":"","ItemInternalId":"bccbd2fe-edf8-4cd9-a72f-9afad7b66bb7","id":13,"url":"https://startup8613.zendesk.com/api/v2/tickets/13.json","subject":"SAMPLE TICKET: Need less items than ordered","raw_subject":"SAMPLE TICKET: Need less items than ordered","description":"Hi! Yesterday I placed an order on your site but accidentaly ordered 3 lamps instead of 2. Can I still change that?","priority":"urgent","status":"open","requester_id":33719984468371,"submitter_id":33719984468371,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:27Z","updated_at":"2024-09-24T01:36:59Z","key":"13" + },{ + "@odata.etag":"","ItemInternalId":"064dfb3e-da9a-46f1-83e2-d4e49956bcb5","id":12,"url":"https://startup8613.zendesk.com/api/v2/tickets/12.json","subject":"SAMPLE TICKET: Cancel order","raw_subject":"SAMPLE TICKET: Cancel order","description":"Can I still cancel my order?","priority":"urgent","status":"open","requester_id":33720000283795,"submitter_id":33720000283795,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:27Z","updated_at":"2024-09-24T01:37:20Z","key":"12" + },{ + "@odata.etag":"","ItemInternalId":"bbbbcec7-c189-473d-ace6-65e1b928ad37","id":9,"url":"https://startup8613.zendesk.com/api/v2/tickets/9.json","subject":"SAMPLE TICKET: Free repair","raw_subject":"SAMPLE TICKET: Free repair","description":"G\u2019day. My cat shredded my brand new armchair today. The leather now has holes in it. Do you offer a repair service or is there a warranty on my chair?","priority":"normal","status":"open","requester_id":33720000222995,"submitter_id":33720000222995,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:25Z","updated_at":"2024-09-24T01:37:09Z","key":"9" + },{ + "@odata.etag":"","ItemInternalId":"857f203b-3920-43ef-ab68-3348e94be439","id":18,"url":"https://startup8613.zendesk.com/api/v2/tickets/18.json","subject":"SAMPLE TICKET: Shipping cost","raw_subject":"SAMPLE TICKET: Shipping cost","description":"Hello, I\u2019ve got some cool items in my cart on your site, but before I take the plunge, I want to understand how much I\u2019ll be paying for shipping. The numbers can be a bit scary when you don\u2019t know what they\u2019re for.\n\nCan you help me understand what all influences the shipping costs? Is there a calculator or formula I can use first?","priority":"normal","status":"open","requester_id":33719937265939,"submitter_id":33719937265939,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:30Z","updated_at":"2024-09-24T01:34:30Z","key":"18" + },{ + "@odata.etag":"","ItemInternalId":"e172d475-e80b-4f15-abbd-345c8855afe3","id":15,"url":"https://startup8613.zendesk.com/api/v2/tickets/15.json","subject":"SAMPLE TICKET: Putting it together?","raw_subject":"SAMPLE TICKET: Putting it together?","description":"Hello, I just placed my order and I\u2019m eager to set up my new furniture when it arrives, but must admit, I\u2019m not very handy, so, uh, I\u2019m not confident about the assembly part.\n\nAre there online instructions? What should I do if I find the assembly steps too difficult or complicated?\n\nKind regards,\nCarlos Garcia","priority":"normal","status":"open","requester_id":33720000038803,"submitter_id":33720000038803,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:28Z","updated_at":"2024-09-24T01:34:28Z","key":"15" + },{ + "@odata.etag":"","ItemInternalId":"51a15f8c-ea29-4b31-b2d2-dcc85b142bd0","id":14,"url":"https://startup8613.zendesk.com/api/v2/tickets/14.json","subject":"SAMPLE TICKET: Where\u2019s it made?","raw_subject":"SAMPLE TICKET: Where\u2019s it made?","description":"Hi, I\u2019m relatively new to furniture shopping and was introduced to Homebuy by a friend. I visited your online store and I\u2019m really interested in your furniture collections, but I\u2019m concerned about where things are made.\n\nAlso, I see that you offer something called \"direct-to-consumer\" payment plans. Could you explain a bit more about what that involves?\n\nThanks in advance for your time,\nTaylor Moore","priority":"normal","status":"open","requester_id":33719937084435,"submitter_id":33719937084435,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:28Z","updated_at":"2024-09-24T01:34:28Z","key":"14" + },{ + "@odata.etag":"","ItemInternalId":"f9dea1b4-199a-47e1-aedb-25741b62b158","id":22,"url":"https://startup8613.zendesk.com/api/v2/tickets/22.json","subject":"SAMPLE TICKET: How does layaway work?","raw_subject":"SAMPLE TICKET: How does layaway work?","description":"Hello. I\u2019m about to purchase a few items from your Urban Chic collection, but the total is a bit much to pay up front. I\u2019ve noticed that you offer a couple of payment options that I\u2019m considering, but I would love some more details. Can you explain the difference between how the Homebuy Layaway and Homebuy Installment Plan work? Can I do both?\n\nAlso, are there any specific thresholds or conditions that I should be aware of while opting for these payment plans?\n\nLooking forward to your response,\nJakub W\u00f3jcik","priority":"normal","status":"open","requester_id":33719969757587,"submitter_id":33719969757587,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:31Z","updated_at":"2024-09-24T01:34:31Z","key":"22" + },{ + "@odata.etag":"","ItemInternalId":"1b7d1102-c808-4c31-a27a-e6ea8d482ad1","id":17,"url":"https://startup8613.zendesk.com/api/v2/tickets/17.json","subject":"SAMPLE TICKET: New delivery address","raw_subject":"SAMPLE TICKET: New delivery address","description":"Hi there! Looks like I jumped the gun and, uh-oh, put the wrong delivery address on my order. I need to fix this fast but I\u2019m not sure how to go about it.\nCan you help me change the delivery address for my order? Or can I do it on the website? Also, is there a deadline for when I can make this change?\n\nThanks for your help!\n\nIngrid Van Dijk","priority":"normal","status":"open","requester_id":33719955946643,"submitter_id":33719955946643,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:29Z","updated_at":"2024-09-24T01:34:29Z","key":"17" + },{ + "@odata.etag":"","ItemInternalId":"42bdcae9-bbd5-4102-a69b-1d3de6ffae47","id":10,"url":"https://startup8613.zendesk.com/api/v2/tickets/10.json","subject":"SAMPLE TICKET: Missing assembly instructions","raw_subject":"SAMPLE TICKET: Missing assembly instructions","description":"Hi, I recently ordered a wardrobe from your website. It arrived yesterday but it looks like the assembly instructions are missing.","priority":"urgent","status":"open","requester_id":33719969757587,"submitter_id":33719969757587,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:25Z","updated_at":"2024-09-24T01:36:52Z","key":"10" + },{ + "@odata.etag":"","ItemInternalId":"11123242-c3d2-4bc6-8655-a10214fe6231","id":4,"url":"https://startup8613.zendesk.com/api/v2/tickets/4.json","subject":"SAMPLE TICKET: Item restock","raw_subject":"SAMPLE TICKET: Item restock","description":"Hello! I noticed that your wattle cushion is out of stock. I want to purchase it for my friend\u2019s birthday next month, do you know when it will be restocked?","priority":"normal","status":"open","requester_id":33719955916179,"submitter_id":33719955916179,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:21Z","updated_at":"2024-09-24T01:35:21Z","key":"4" + },{ + "@odata.etag":"","ItemInternalId":"fff0231c-3f40-4e5d-8775-95b6516b9795","id":7,"url":"https://startup8613.zendesk.com/api/v2/tickets/7.json","subject":"SAMPLE TICKET: Wrong address","raw_subject":"SAMPLE TICKET: Wrong address","description":"Hi there, I just purchased an item and realized I used the wrong delivery address. Can you help me?","priority":"urgent","status":"open","requester_id":33720000167699,"submitter_id":33720000167699,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:23Z","updated_at":"2024-09-24T01:37:16Z","key":"7" + },{ + "@odata.etag":"","ItemInternalId":"4db1c873-30cc-4d21-a0b6-a2ae730797ef","id":3,"url":"https://startup8613.zendesk.com/api/v2/tickets/3.json","subject":"SAMPLE TICKET: Order status says it is still processing","raw_subject":"SAMPLE TICKET: Order status says it is still processing","description":"My order status has been stuck on processing for 3 days. Why hasn\u2019t it been shipped yet?","priority":"urgent","status":"open","requester_id":33720000038803,"submitter_id":33720000038803,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:21Z","updated_at":"2024-09-24T01:36:39Z","key":"3" + },{ + "@odata.etag":"","ItemInternalId":"184eb7ea-8cb6-4b1c-b0b4-4deb01500fba","id":2,"url":"https://startup8613.zendesk.com/api/v2/tickets/2.json","subject":"SAMPLE TICKET: Damaged product","raw_subject":"SAMPLE TICKET: Damaged product","description":"Hi there, I received my armchair today in the mail and I noticed that the material has a tear in it.\n\nI would like a replacement sent to me or to be issued a refund.","priority":"urgent","status":"open","requester_id":33719937084435,"submitter_id":33719937084435,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:20Z","updated_at":"2024-09-24T01:36:35Z","key":"2" + },{ + "@odata.etag":"","ItemInternalId":"21a25035-d6a3-4c0f-b196-ec8964c05ee7","id":5,"url":"https://startup8613.zendesk.com/api/v2/tickets/5.json","subject":"SAMPLE TICKET: International shipping","raw_subject":"SAMPLE TICKET: International shipping","description":"Hello, I would like to buy one of your products and send it to my friend in Portugal. Do you ship overseas?","priority":"normal","status":"open","requester_id":33719955946643,"submitter_id":33719955946643,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:22Z","updated_at":"2024-09-24T01:35:25Z","key":"5" + },{ + "@odata.etag":"","ItemInternalId":"9436acec-75a0-4678-aeac-b2ce5515c94b","id":6,"url":"https://startup8613.zendesk.com/api/v2/tickets/6.json","subject":"SAMPLE TICKET: Are products ethically sourced","raw_subject":"SAMPLE TICKET: Are products ethically sourced","description":"Hi, I\u2019m interested in some of your products, but want to know if your materials are sustainably and ethically sourced before purchasing.","priority":"low","status":"open","requester_id":33719937265939,"submitter_id":33719937265939,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:34:23Z","updated_at":"2024-09-24T01:35:28Z","key":"6" + },{ + "@odata.etag":"","ItemInternalId":"c0a50c4f-5c67-44bf-a0c0-aab0ef66b0ae","id":1,"url":"https://startup8613.zendesk.com/api/v2/tickets/1.json","type":"incident","subject":"SAMPLE TICKET: Meet the ticket","raw_subject":"SAMPLE TICKET: Meet the ticket","description":"Hi there,\n\nI\u2019m sending an email because I\u2019m having a problem setting up your new product. Can you help me troubleshoot?\n\nThanks,\n The Customer","priority":"normal","status":"open","requester_id":33719944623507,"submitter_id":33719960414739,"assignee_id":33719960414739,"group_id":33719944452883,"has_incidents":false,"ticket_form_id":33719944367635,"brand_id":33719928618899,"created_at":"2024-09-24T01:32:27Z","updated_at":"2024-09-24T01:32:27Z","key":"1" + } + ] +} diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetSchema.json b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetSchema.json new file mode 100644 index 0000000000..b46cb8ba2d --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/Responses/ZD Tickets GetSchema.json @@ -0,0 +1,271 @@ +{ + "name": "tickets", + "title": "tickets", + "x-ms-permission": "read-write", + "x-ms-capabilities": { + "sortRestrictions": { + "sortable": true, + "unsortableProperties": [ "collaborator_ids", "tags", "via", "custom_fields", "satisfaction_rating", "sharing_agreement_ids", "followup_ids" ] + }, + "filterRestrictions": { + "filterable": true, + "nonFilterableProperties": [ "collaborator_ids", "tags", "via", "custom_fields", "satisfaction_rating", "sharing_agreement_ids", "followup_ids" ] + }, + "selectRestrictions": { "selectable": true }, + "filterFunctionSupport": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "now", "not", "and", "or", "day", "month", "year", "hour", "minute", "second", "date", "time", "totaloffsetminutes", "totalseconds", "round", "floor", "ceiling", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat", "sum", "min", "max", "average", "countdistinct", "null" ] + }, + "schema": { + "type": "array", + "items": { + "type": "object", + "required": [], + "properties": { + "id": { + "title": "id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-keyOrder": 1, + "x-ms-keyType": "primary", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "url": { + "title": "url", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "external_id": { + "title": "external_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "type": { + "title": "type", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "subject": { + "title": "subject", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "raw_subject": { + "title": "raw_subject", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "description": { + "title": "description", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "priority": { + "title": "priority", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "enum": [ + "low", + "normal", + "high", + "urgent" + ], + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "status": { + "title": "status", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "recipient": { + "title": "recipient", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "contains", "startswith", "endswith", "length", "indexof", "replace", "substring", "substringof", "tolower", "toupper", "trim", "concat" ] }, + "type": "string", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "requester_id": { + "title": "requester_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "submitter_id": { + "title": "submitter_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "assignee_id": { + "title": "assignee_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "organization_id": { + "title": "organization_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "group_id": { + "title": "group_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "collaborator_ids": { + "title": "collaborator_ids", + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "forum_topic_id": { + "title": "forum_topic_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "problem_id": { + "title": "problem_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "has_incidents": { + "title": "has_incidents", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "not", "and", "or" ] }, + "type": "boolean", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "due_at": { + "title": "due_at", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "day", "month", "year", "hour", "minute", "second", "date", "time", "totaloffsetminutes" ] }, + "type": "string", + "format": "date-time", + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "tags": { + "title": "tags", + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "via": { + "title": "via", + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "custom_fields": { + "title": "custom_fields", + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "satisfaction_rating": { + "title": "satisfaction_rating", + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "sharing_agreement_ids": { + "title": "sharing_agreement_ids", + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "followup_ids": { + "title": "followup_ids", + "type": "string", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "ticket_form_id": { + "title": "ticket_form_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "brand_id": { + "title": "brand_id", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "add", "sub", "mul", "div", "mod", "negate", "sum", "average" ] }, + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807, + "x-ms-permission": "read-write", + "x-ms-sort": "none" + }, + "created_at": { + "title": "created_at", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "day", "month", "year", "hour", "minute", "second", "date", "time", "totaloffsetminutes" ] }, + "type": "string", + "format": "date-time", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + }, + "updated_at": { + "title": "updated_at", + "x-ms-capabilities": { "filterFunctions": [ "lt", "le", "eq", "ne", "gt", "ge", "min", "max", "countdistinct", "day", "month", "year", "hour", "minute", "second", "date", "time", "totaloffsetminutes" ] }, + "type": "string", + "format": "date-time", + "x-ms-permission": "read-only", + "x-ms-sort": "none" + } + } + }, + "x-ms-permission": "read-write" + } +} diff --git a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ThreadingTests.cs b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ThreadingTests.cs index 6679ebd2b5..2885ce6fb3 100644 --- a/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ThreadingTests.cs +++ b/src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/ThreadingTests.cs @@ -21,7 +21,8 @@ public void CheckConnector() var bugsFieldType = new HashSet(); var bugNames = new HashSet() { - "ConnectorFunction._slash" + "ConnectorFunction._slash", + "ColumnCapabilities.DefaultFilterFunctionSupport" }; AnalyzeThreadSafety.CheckStatics(asm, bugsFieldType, bugNames); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DelegationTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DelegationTests.cs new file mode 100644 index 0000000000..3e7a1ed7e3 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DelegationTests.cs @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.PowerFx.Core.Entities; +using Microsoft.PowerFx.Core.Functions.Delegation; +using Microsoft.PowerFx.Core.Utils; +using Microsoft.PowerFx.Syntax; +using Microsoft.PowerFx.Types; +using Xunit; + +namespace Microsoft.PowerFx.Core.Tests +{ + public class DelegationTests + { + [Fact] + public void CapabilityTest() + { + RecordType rt = new TestRecordType("myTable", RecordType.Empty().Add("logic", FormulaType.String, "display"), null); + + Assert.NotNull(rt._type.AssociatedDataSources); + Assert.NotEmpty(rt._type.AssociatedDataSources); + + IExternalTabularDataSource externalDataSource = rt._type.AssociatedDataSources.First(); + + Assert.True(externalDataSource.IsDelegatable); + + IDelegationMetadata delegationMetadata = externalDataSource.DelegationMetadata; + + bool eq = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.Equal, DPath.Root.Append(new DName("logic"))); + bool neq = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.NotEqual, DPath.Root.Append(new DName("logic"))); + bool eq2 = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.Equal, DPath.Root.Append(new DName("logic2"))); + bool neq2 = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.NotEqual, DPath.Root.Append(new DName("logic2"))); + + Assert.True(eq); + Assert.False(neq); + Assert.False(eq2); + Assert.False(neq2); + } + + [Fact] + public void DelegationOperatorTest() + { + string enums = string.Join(", ", Enum.GetNames(typeof(DelegationOperator)).Select(name => name.ToLowerInvariant()).OrderBy(x => x)); + string constants = string.Join(", ", typeof(DelegationMetadataOperatorConstants).GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).Select(fi => (string)fi.GetValue(null)).OrderBy(x => x)); + Assert.Equal(enums, constants); + } + } + + public class TestRecordType : RecordType + { + private readonly RecordType _recordType; + private readonly List _allowedFilters; + + public TestRecordType(string tableName, RecordType recordType, List allowedFilters) + : base(GetDisplayNameProvider(recordType), GetDelegationInfo(tableName, recordType)) + { + _recordType = recordType; + _allowedFilters = allowedFilters; + } + + public override bool TryGetFieldType(string fieldName, out FormulaType type) + { + return _recordType.TryGetFieldType(fieldName, out type); + } + + private static DisplayNameProvider GetDisplayNameProvider(RecordType recordType) + { + return DisplayNameProvider.New(recordType.FieldNames.Select(f => new KeyValuePair(new DName(f), new DName(f)))); + } + + private static TableDelegationInfo GetDelegationInfo(string tableName, RecordType recordType) + { + return new TestDelegationInfo(recordType) + { + TableName = tableName + }; + } + + public override bool Equals(object other) + { + if (other == null || other is not TestRecordType other2) + { + return false; + } + + return _recordType == other2._recordType; + } + +#pragma warning disable CA1065 // Exceptions should not be raised in this type of method. + public override int GetHashCode() => throw new NotImplementedException(); +#pragma warning restore CA1065 + } + + public class TestDelegationInfo : TableDelegationInfo + { + private readonly RecordType _recordType; + + public TestDelegationInfo(RecordType recordType) + : base() + { + _recordType = recordType; + } + + public override bool IsDelegable => true; + + public override ColumnCapabilitiesDefinition GetColumnCapability(string fieldName) + { + if (_recordType.TryGetFieldType(fieldName, out FormulaType ft)) + { + return new ColumnCapabilitiesDefinition() + { + FilterFunctions = new List() { DelegationOperator.Eq } + }; + } + + return null; + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameOptionSetTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameOptionSetTests.cs index 419fa31593..d0c6968317 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameOptionSetTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/DisplayNameOptionSetTests.cs @@ -34,6 +34,12 @@ public void OptionSetDisplayNames(string inputExpression, string outputExpressio })); config.AddOptionSet(optionSet, string.IsNullOrEmpty(optionSetDisplayName) ? default : new DName(optionSetDisplayName)); + + IEnumerable> optionSets = config.SymbolTable.OptionSets; + + Assert.Single(optionSets); + Assert.Equal("OptionSet", optionSets.First().Key); + Assert.Equal(optionSet, optionSets.First().Value); var engine = new Engine(config); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AsType_UO.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AsType_UO.txt index 54f38fe36e..b294c06ac7 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AsType_UO.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/AsType_UO.txt @@ -130,7 +130,7 @@ Errors: Error 26-33: Name isn't valid. 'UnKnown' isn't recognized.|Error 0-34: I Error({Kind:ErrorKind.InvalidJSON}) >> AsType(ParseJSON("true"), Void) -Errors: Error 26-30: Unsupported untyped/JSON conversion type 'Void' in argument. +Errors: Error 26-30: Unsupported type 'Void' in type argument. >> AsType(ParseJSON("{""a"": 5, ""b"":true}"), Type({a: Number})) Error({Kind:ErrorKind.InvalidArgument}) @@ -139,13 +139,13 @@ Error({Kind:ErrorKind.InvalidArgument}) Errors: Error 0-59: Invalid number of arguments: received 3, expected 2. >> AsType(ParseJSON("true"), None) -Errors: Error 26-30: Unsupported untyped/JSON conversion type 'ObjNull' in argument. +Errors: Error 26-30: Unsupported type 'ObjNull' in type argument. >> AsType(ParseJSON("null"), None) -Errors: Error 26-30: Unsupported untyped/JSON conversion type 'ObjNull' in argument. +Errors: Error 26-30: Unsupported type 'ObjNull' in type argument. >> AsType(ParseJSON("{}"), Type({a: Text, b: [Color]})) -Errors: Error 28-29: Unsupported untyped/JSON conversion type 'Color' in argument. +Errors: Error 28-29: Unsupported type 'Color' in type argument. >> AsType(If(1/0 > 1, ParseJSON("42")), Number) Error({Kind:ErrorKind.Div0}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsEmpty.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsEmpty.txt index 303594466f..f7ceb85dc6 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsEmpty.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsEmpty.txt @@ -114,6 +114,16 @@ true ),a > 10)) true +// Untyped object +>> IsEmpty(ParseJSON("[{""a"":1}]")) +false + +>> IsEmpty(ParseJSON("[]")) +true + +>> IsEmpty(ParseJSON("1")) +Error({Kind:ErrorKind.InvalidArgument}) + // INVALID ARGUMENTS >> IsEmpty("") Errors: Error 8-10: Invalid argument type (Text). Expecting a Table value instead. diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsType_UO.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsType_UO.txt index 1cb3b70494..f50e69da5d 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsType_UO.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/IsType_UO.txt @@ -107,16 +107,16 @@ Errors: Error 26-33: Name isn't valid. 'UnKnown' isn't recognized.|Error 0-34: I Error({Kind:ErrorKind.InvalidJSON}) >> IsType(ParseJSON("true"), Void) -Errors: Error 26-30: Unsupported untyped/JSON conversion type 'Void' in argument. +Errors: Error 26-30: Unsupported type 'Void' in type argument. >> IsType(ParseJSON("{""a"": 5}"), Type({a: Number}), "Hello") Errors: Error 0-59: Invalid number of arguments: received 3, expected 2. >> IsType(ParseJSON("true"), None) -Errors: Error 26-30: Unsupported untyped/JSON conversion type 'ObjNull' in argument. +Errors: Error 26-30: Unsupported type 'ObjNull' in type argument. >> IsType(ParseJSON("{}"), Type({a: Text, b: [Color]})) -Errors: Error 28-29: Unsupported untyped/JSON conversion type 'Color' in argument. +Errors: Error 28-29: Unsupported type 'Color' in type argument. >> IsType(If(1/0 > 1, ParseJSON("42")), Number) Error({Kind:ErrorKind.Div0}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON.txt index eb9c58e861..00375c062a 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON.txt @@ -1,4 +1,4 @@ -#SETUP: EnableJsonFunctions, PowerFxV1CompatibilityRules +#SETUP: EnableJsonFunctions >> JSON() Errors: Error 0-6: Invalid number of arguments: received 0, expected 1-2. @@ -211,9 +211,6 @@ Error({Kind:ErrorKind.InvalidArgument}) >> JSON(ParseJSON("[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]")) Error({Kind:ErrorKind.InvalidArgument}) ->> JSON(Decimal(ParseJSON("123456789012345.6789012345678"))) -"123456789012345.6789012345678" - >> JSON(ParseJSON("123456789012345.6789012345678")) "123456789012345.6789012345678" diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1Compat.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1Compat.txt index c64f936248..1b57b3c355 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1Compat.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/JSON_V1Compat.txt @@ -3,3 +3,6 @@ // Error for unknown options in the second argument >> JSON({a:1,b:[1,2,3]}, "_U") Errors: Error 22-26: Invalid argument type (Text). Expecting a Enum (JSONFormat) value instead. + +>> JSON(Decimal(ParseJSON("123456789012345.6789012345678"))) +"123456789012345.6789012345678" diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/PlainText.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/PlainText.txt index 65540716c4..0446edc335 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/PlainText.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/PlainText.txt @@ -1,4 +1,4 @@ ->> PlainText("") +>> PlainText("") "" >> PlainText("<>") @@ -97,4 +97,13 @@ Error({Kind:ErrorKind.Div0}) "1\r\n\r\n2\r\n\r\n3\r\n\r\n4" >> PlainText("Hello<br/>world") -"Hello
world" \ No newline at end of file +"Hello
world" + +>> PlainText("Many character entities: <>&"'¢£¥€©®áèõçûαβγδΔ") +"Many character entities: <>&""'¢£¥€©®áèõçûαβγδΔ" + +>> PlainText("More character references: 🥰 - 🥈") +"More character references: 🥰 - 🥈" + +>> PlainText("Not a line break.Also not a line break.") +"Not a line break.Also not a line break." diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TypedParseJSON.txt b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TypedParseJSON.txt index 1ebd178a70..abca5ad9ce 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TypedParseJSON.txt +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/ExpressionTestCases/TypedParseJSON.txt @@ -121,7 +121,7 @@ Error({Kind:ErrorKind.InvalidArgument}) Errors: Error 15-16: Invalid argument '1'. Expected valid type name or type literal. >> ParseJSON("""RED""", Color) -Errors: Error 21-26: Unsupported untyped/JSON conversion type 'Color' in argument. +Errors: Error 21-26: Unsupported type 'Color' in type argument. >> ParseJSON("5", Type(5)) Errors: Error 20-21: Type literal declaration is invalid. The expression '5' cannot be used in a type definition.|Error 19-20: Type literal declaration is invalid. The expression 'Type(5)' cannot be used in a type definition. @@ -133,7 +133,7 @@ Errors: Error 18-25: Name isn't valid. 'UnKnown' isn't recognized.|Error 0-26: I Error({Kind:ErrorKind.InvalidJSON}) >> ParseJSON("true", Void) -Errors: Error 18-22: Unsupported untyped/JSON conversion type 'Void' in argument. +Errors: Error 18-22: Unsupported type 'Void' in type argument. >> ParseJSON("{""a"": 5, ""b"":true}", Type({a: Number})) Error({Kind:ErrorKind.InvalidArgument}) @@ -142,13 +142,13 @@ Error({Kind:ErrorKind.InvalidArgument}) Errors: Error 0-51: Invalid number of arguments: received 3, expected 2. >> ParseJSON("true", None) -Errors: Error 18-22: Unsupported untyped/JSON conversion type 'ObjNull' in argument. +Errors: Error 18-22: Unsupported type 'ObjNull' in type argument. >> ParseJSON("null", None) -Errors: Error 18-22: Unsupported untyped/JSON conversion type 'ObjNull' in argument. +Errors: Error 18-22: Unsupported type 'ObjNull' in type argument. >> ParseJSON("{}", Type({a: Text, b: [Color]})) -Errors: Error 20-21: Unsupported untyped/JSON conversion type 'Color' in argument. +Errors: Error 20-21: Unsupported type 'Color' in type argument. >> ParseJSON(If(1/0 > 1, "42"), Number) Error({Kind:ErrorKind.Div0}) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Extensions.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Extensions.cs index ea0a1b6a31..751018635d 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Extensions.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Extensions.cs @@ -36,19 +36,14 @@ public static string GetErrBehaviorPropertyExpectedMessage() } public static string ToStringWithDisplayNames(this FormulaType ftype) - { - return ftype._type.ToStringWithDisplayNames(); - } - - internal static string ToStringWithDisplayNames(this DType dtype) { var sb = new StringBuilder(); - sb.AppendToWithDisplayNames(dtype); + sb.AppendToWithDisplayNames(ftype._type, ftype); return sb.ToString(); } - internal static string AppendToWithDisplayNames(this StringBuilder sb, DType dtype) - { + internal static string AppendToWithDisplayNames(this StringBuilder sb, DType dtype, FormulaType ftype) + { sb.Append(DType.MapKindToStr(dtype.Kind)); switch (dtype.Kind) @@ -57,6 +52,10 @@ internal static string AppendToWithDisplayNames(this StringBuilder sb, DType dty case DKind.Table: AppendAggregateType(sb, dtype.TypeTree, dtype.DisplayNameProvider); break; + case DKind.LazyRecord: + case DKind.LazyTable: + AppendAggregateLazyType(sb, ftype as AggregateType ?? throw new InvalidOperationException("Not a valid type")); + break; case DKind.OptionSet: case DKind.View: AppendOptionSetOrViewType(sb, dtype.TypeTree, dtype.DisplayNameProvider); @@ -79,14 +78,14 @@ private static void AppendOptionSetValue(StringBuilder sb, IExternalOptionSet op sb.Append('('); bool first = true; - foreach (DName name in es.OptionNames) + foreach (DName name in es.OptionNames.OrderBy(dn => dn.Value, StringComparer.Ordinal)) { if (!first) { sb.Append(','); } - - first = false; + + first = false; sb.Append(TexlLexer.EscapeName(name.Value)); if (es.TryLookupValueByName(name.Value, out object value)) @@ -107,7 +106,7 @@ private static void AppendAggregateType(StringBuilder sb, TypeTree tree, Display sb.Append("["); var strPre = string.Empty; - foreach (var kvp in tree.GetPairs()) + foreach (var kvp in tree.GetPairs().OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) { Contracts.Assert(kvp.Value.IsValid); sb.Append(strPre); @@ -122,7 +121,71 @@ private static void AppendAggregateType(StringBuilder sb, TypeTree tree, Display } sb.Append(":"); - sb.AppendToWithDisplayNames(kvp.Value); + sb.AppendToWithDisplayNames(kvp.Value, null); + strPre = ", "; + } + + sb.Append("]"); + } + + private static void AppendAggregateLazyType(StringBuilder sb, AggregateType fType) + { + Contracts.AssertValue(sb); + + sb.Append("["); + + var strPre = string.Empty; + foreach (string fieldName in fType.FieldNames.OrderBy(f => f, StringComparer.Ordinal)) + { + sb.Append(strPre); + sb.Append(TexlLexer.EscapeName(fieldName)); + + string display = fType._type.DisplayNameProvider.LogicalToDisplayPairs.FirstOrDefault(kvp => kvp.Key.Value == fieldName).Value.Value; + + if (!string.IsNullOrEmpty(display) && TexlLexer.EscapeName(display) != TexlLexer.EscapeName(fieldName)) + { + sb.Append("`"); + sb.Append(TexlLexer.EscapeName(display)); + } + + sb.Append(":"); + + IExternalTabularDataSource ads = fType._type.AssociatedDataSources.FirstOrDefault(); + DataSourceInfo dataSourceInfo = ads as DataSourceInfo; + + if (dataSourceInfo == null && fType._type.TryGetType(new DName(fieldName), out DType type)) + { + sb.Append(type.ToString()); + } + else if (dataSourceInfo != null) + { + if (dataSourceInfo.ColumnsWithRelationships.TryGetValue(fieldName, out string remoteTable)) + { + sb.Append('~'); + sb.Append(remoteTable); + sb.Append(':'); + + if (!dataSourceInfo.RecordType.TryGetUnderlyingFieldType(fieldName, out FormulaType backingFieldType)) + { + throw new InvalidOperationException(); + } + + sb.Append(backingFieldType._type.ToString()); + } + else if (ads.Type.TryGetType(new DName(fieldName), out DType type2)) + { + sb.Append(type2.ToString()); + } + else + { + sb.Append('§'); + } + } + else + { + sb.Append("§§"); + } + strPre = ", "; } @@ -136,7 +199,7 @@ private static void AppendOptionSetOrViewType(StringBuilder sb, TypeTree tree, D sb.Append("{"); var strPre = string.Empty; - foreach (var kvp in tree.GetPairs()) + foreach (var kvp in tree.GetPairs().OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) { Contracts.Assert(kvp.Value.IsValid); sb.Append(strPre); @@ -151,7 +214,7 @@ private static void AppendOptionSetOrViewType(StringBuilder sb, TypeTree tree, D } sb.Append(":"); - sb.AppendToWithDisplayNames(kvp.Value); + sb.AppendToWithDisplayNames(kvp.Value, null); strPre = ", "; } @@ -166,7 +229,7 @@ private static void AppendEnumType(StringBuilder sb, ValueTree tree, DKind enumS sb.Append("["); var strPre = string.Empty; - foreach (var kvp in tree.GetPairs()) + foreach (var kvp in tree.GetPairs().OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) { Contracts.AssertNonEmpty(kvp.Key); Contracts.AssertValue(kvp.Value.Object); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FileLocationTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FileLocationTests.cs new file mode 100644 index 0000000000..12d814dc4b --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FileLocationTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.PowerFx.Core.Errors; +using Microsoft.PowerFx.Core.Localization; +using Microsoft.PowerFx.Syntax; +using Xunit; + +namespace Microsoft.PowerFx.Core.Tests +{ + public class FileLocationTests + { + [Theory] + [InlineData(0, 200, 300)] + [InlineData(1, 200, 301)] + [InlineData(4, 201, 300)] + [InlineData(9, 202, 301)] + public void Apply(int min, int expectedLine, int expectedCol) + { + string file = + + // 012 345 6 789 + "ABC\nDE\r\nFG"; + + var loc = new FileLocation + { + Filename = "test", + LineStart = 200, + ColStart = 300 + }; + + var span = new Span(min, min + 1); + var loc2 = loc.Apply(file, span); + + Assert.Same(loc.Filename, loc2.Filename); + Assert.Equal(expectedLine, loc2.LineStart); + Assert.Equal(expectedCol, loc2.ColStart); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs index cfc2945768..3baccab4f1 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/Helpers/TestTabularDataSource.cs @@ -124,7 +124,7 @@ internal class DataEntityMetadata : IDataEntityMetadata public DType Schema => throw new NotImplementedException(); - public BidirectionalDictionary DisplayNameMapping => throw new NotImplementedException(); + public BidirectionalDictionary DisplayNameMapping => new BidirectionalDictionary(); public BidirectionalDictionary PreviousDisplayNameMapping => throw new NotImplementedException(); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs index 9a81a045ac..400377e58d 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/NamedFormulasTests.cs @@ -17,7 +17,7 @@ public class NamedFormulasTests : PowerFxTest private readonly ParserOptions _parseOptions = new ParserOptions() { AllowsSideEffects = true }; [Theory] - [InlineData("Foo = Type(Number);")] + [InlineData("Foo := Type(Number);")] public void DefSimpleTypeTest(string script) { var parserOptions = new ParserOptions() @@ -33,7 +33,7 @@ public void DefSimpleTypeTest(string script) } [Theory] - [InlineData("Foo = Type({ Age: Number });")] + [InlineData("Foo := Type({ Age: Number });")] public void DefRecordTypeTest(string script) { var parserOptions = new ParserOptions() @@ -63,7 +63,7 @@ public void AsTypeTest(string script) } [Theory] - [InlineData("Foo = Type({Age: Number}; Bar(x: Number): Number = Abs(x);")] + [InlineData("Foo := Type({Age: Number}; Bar(x: Number): Number = Abs(x);")] public void FailParsingTest(string script) { var parserOptions = new ParserOptions() diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PublicSurfaceTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PublicSurfaceTests.cs index f771f39e18..6a9a8368f3 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PublicSurfaceTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/PublicSurfaceTests.cs @@ -47,7 +47,6 @@ public void PublicSurface_Tests() "Microsoft.PowerFx.ParseResult", "Microsoft.PowerFx.ParserOptions", "Microsoft.PowerFx.IPostCheckErrorHandler", - "Microsoft.PowerFx.EngineDocumentation", // Config & Symbols @@ -68,6 +67,7 @@ public void PublicSurface_Tests() "Microsoft.PowerFx.Syntax.IdentToken", "Microsoft.PowerFx.Syntax.NumLitToken", "Microsoft.PowerFx.Syntax.Span", + "Microsoft.PowerFx.Syntax.FileLocation", "Microsoft.PowerFx.Syntax.StrLitToken", "Microsoft.PowerFx.Syntax.Token", "Microsoft.PowerFx.Syntax.TokKind", @@ -129,11 +129,11 @@ public void PublicSurface_Tests() "Microsoft.PowerFx.Types.DecimalValue", "Microsoft.PowerFx.Types.DeferredType", "Microsoft.PowerFx.Types.DelegationParameters", - "Microsoft.PowerFx.Types.DelegationParameterFeatures", + "Microsoft.PowerFx.Types.DelegationParameterFeatures", "Microsoft.PowerFx.Types.DValue`1", "Microsoft.PowerFx.Types.ErrorValue", "Microsoft.PowerFx.Types.ExternalType", - "Microsoft.PowerFx.Types.ExternalTypeKind", + "Microsoft.PowerFx.Types.ExternalTypeKind", "Microsoft.PowerFx.Types.FormulaType", "Microsoft.PowerFx.Types.FormulaValue", "Microsoft.PowerFx.Types.GuidType", @@ -143,7 +143,7 @@ public void PublicSurface_Tests() "Microsoft.PowerFx.Types.ITypeVisitor", "Microsoft.PowerFx.Types.IUntypedObject", "Microsoft.PowerFx.Types.UntypedObjectBase", - "Microsoft.PowerFx.Types.IValueVisitor", + "Microsoft.PowerFx.Types.IValueVisitor", "Microsoft.PowerFx.Types.NamedFormulaType", "Microsoft.PowerFx.Types.NamedValue", "Microsoft.PowerFx.Types.NumberType", @@ -192,18 +192,27 @@ public void PublicSurface_Tests() // TBD ... "Microsoft.PowerFx.BasicUserInfo", - "Microsoft.PowerFx.PowerFxFileInfo", "Microsoft.PowerFx.Core.DisplayNameProvider", "Microsoft.PowerFx.Core.DisplayNameUtility", - "Microsoft.PowerFx.Core.Entities.IRefreshable", + "Microsoft.PowerFx.Core.Entities.ColumnCapabilities", + "Microsoft.PowerFx.Core.Entities.ColumnCapabilitiesBase", + "Microsoft.PowerFx.Core.Entities.ColumnCapabilitiesDefinition", + "Microsoft.PowerFx.Core.Entities.FilterRestrictions", + "Microsoft.PowerFx.Core.Entities.GroupRestrictions", + "Microsoft.PowerFx.Core.Entities.IRefreshable", + "Microsoft.PowerFx.Core.Entities.SelectionRestrictions", + "Microsoft.PowerFx.Core.Entities.SortRestrictions", + "Microsoft.PowerFx.Core.Entities.TableDelegationInfo", + "Microsoft.PowerFx.Core.Functions.Delegation.DelegationOperator", "Microsoft.PowerFx.Core.Localization.ErrorResourceKey", "Microsoft.PowerFx.Core.RenameDriver", "Microsoft.PowerFx.Core.Utils.DName", "Microsoft.PowerFx.Core.Utils.DPath", "Microsoft.PowerFx.Core.Utils.ICheckable", - "Microsoft.PowerFx.UserInfo", "Microsoft.PowerFx.Logging.ITracer", - "Microsoft.PowerFx.Logging.TraceSeverity" + "Microsoft.PowerFx.Logging.TraceSeverity", + "Microsoft.PowerFx.PowerFxFileInfo", + "Microsoft.PowerFx.UserInfo" }; var sb = new StringBuilder(); diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/SymbolTableTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/SymbolTableTests.cs index 9520f94cc7..288db61b87 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/SymbolTableTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/SymbolTableTests.cs @@ -306,6 +306,25 @@ public void ComposedReadOnlySymbolTableFunctionCacheTest() Assert.Equal(2, func3.Count()); } + [Fact] + public void OptionSetTests() + { + var os1 = new OptionSet("os1", DisplayNameProvider.New(new Dictionary() { { new DName("ln1"), new DName("dn1") } })); + var os2 = new OptionSet("os2", DisplayNameProvider.New(new Dictionary() { { new DName("ln2"), new DName("dn2") }, { new DName("ln3"), new DName("dn3") } })); + + var st1 = new SymbolTable(); + st1.AddOptionSet(os1); + + Assert.Single(st1.OptionSets); + + var st2 = new SymbolTable(); + st2.AddOptionSet(os2); + + var st3 = SymbolTable.Compose(st1, st2); + + Assert.Equal(2, st3.OptionSets.Count()); + } + [Fact] public void VoidIsNotAllowed() { diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/DTypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/DTypeTests.cs index c432b6dfb1..290660d1ce 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/DTypeTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/TypeSystemTests/DTypeTests.cs @@ -1102,6 +1102,34 @@ public void RecordAndTableDTypeTests() Assert.True(type11.GetType(DPath.Root.Append(new DName("A"))) == DType.Error); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RecordAndTableFieldOrder(bool useRecord) + { + var type = useRecord ? DType.EmptyRecord : DType.EmptyTable; + + var fieldNames = new[] { "C", "ß", "ç", "A", "b", "á", "é", "ss", "word", "wórd" }; + var expectedFieldOrder = new List(fieldNames); + expectedFieldOrder.Sort(StringComparer.Ordinal); + + foreach (var fieldName in fieldNames) + { + type = type.Add(new DName(fieldName), DType.String); + } + + var expectedTypeStr = + (useRecord ? "!" : "*") + + "[" + + string.Join(", ", expectedFieldOrder.Select(f => f + ":s")) + + "]"; + + Assert.Equal(expectedTypeStr, type.ToString()); + + var actualFieldNames = type.GetNames(DPath.Root).Select(tn => tn.Name.Value).ToArray(); + Assert.Equal(expectedFieldOrder, actualFieldNames); + } + [Fact] public void EnumDTypeTests() { diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs index a9d10f5337..163e732dc1 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/UserDefinedTypeTests.cs @@ -22,24 +22,24 @@ public class UserDefinedTypeTests : PowerFxTest [Theory] // Check record, table types with primitive types - [InlineData("Point = Type({ x: Number, y: Number })", "![x:n,y:n]", true)] - [InlineData("Points = Type([{ x: Number, y: Number }])", "*[x:n,y:n]", true)] - [InlineData("Person = Type({ name: Text, dob: Date })", "![name:s,dob:D]", true)] - [InlineData("People = Type([{ name: Text, isReady: Boolean }])", "*[name:s,isReady:b]", true)] - [InlineData("Heights = Type([Number])", "*[Value:n]", true)] - [InlineData("Palette = Type([Color])", "*[Value:c]", true)] + [InlineData("Point := Type({ x: Number, y: Number })", "![x:n,y:n]", true)] + [InlineData("Points := Type([{ x: Number, y: Number }])", "*[x:n,y:n]", true)] + [InlineData("Person := Type({ name: Text, dob: Date })", "![name:s,dob:D]", true)] + [InlineData("People := Type([{ name: Text, isReady: Boolean }])", "*[name:s,isReady:b]", true)] + [InlineData("Heights := Type([Number])", "*[Value:n]", true)] + [InlineData("Palette := Type([Color])", "*[Value:c]", true)] // Type alias - [InlineData("DTNZ = Type(DateTimeTZInd)", "Z", true)] + [InlineData("DTNZ := Type(DateTimeTZInd)", "Z", true)] // Nested record types - [InlineData("Nested = Type({a: {b: DateTime, c: {d: GUID, e: Hyperlink}}, x: Time})", "![a:![b:d, c:![d:g, e:h]], x:T]", true)] + [InlineData("Nested := Type({a: {b: DateTime, c: {d: GUID, e: Hyperlink}}, x: Time})", "![a:![b:d, c:![d:g, e:h]], x:T]", true)] // Invalid types - [InlineData("Pics = Type([Image])", "*[Value:i]", false)] - [InlineData("A = Type(B)", "", false)] - [InlineData("A = Type([])", "", false)] - [InlineData("A = Type({})", "", false)] + [InlineData("Pics := Type([Image])", "*[Value:i]", false)] + [InlineData("A := Type(B)", "", false)] + [InlineData("A := Type([])", "", false)] + [InlineData("A := Type({})", "", false)] public void TestUserDefinedType(string typeDefinition, string expectedDefinedTypeString, bool isValid) { var parseOptions = new ParserOptions @@ -66,38 +66,38 @@ public void TestUserDefinedType(string typeDefinition, string expectedDefinedTyp } [Theory] - [InlineData("X = 5; Point = Type({ x: Number, y: Number })", 1)] - [InlineData("Point = Type({ x: Number, y: Number }); Points = Type([Point])", 2)] + [InlineData("X = 5; Point := Type({ x: Number, y: Number })", 1)] + [InlineData("Point := Type({ x: Number, y: Number }); Points := Type([Point])", 2)] // Mix named formula with named type - [InlineData("X = 5; Point = Type({ x: X, y: Number }); Points = Type([Point])", 0)] + [InlineData("X = 5; Point := Type({ x: X, y: Number }); Points := Type([Point])", 0)] // Have invalid type expression - [InlineData("WrongType = Type(5+5); WrongTypes = Type([WrongType]); People = Type([{ name: Text, age: Number }])", 1)] + [InlineData("WrongType := Type(5+5); WrongTypes := Type([WrongType]); People := Type([{ name: Text, age: Number }])", 1)] // Have incomplete expressions and parse errors - [InlineData("Point = Type({a:); Points = Type([Point]); People = Type([{ name: Text, age })", 0)] - [InlineData("Point = Type({a:; Points = Type([Point]); People = Type([{ name: Text, age: Number })", 1)] + [InlineData("Point := Type({a:); Points = Type([Point]); People := Type([{ name: Text, age })", 0)] + [InlineData("Point := Type({a:; Points = Type([Point]); People := Type([{ name: Text, age: Number })", 1)] // Redeclare type - [InlineData("Point = Type({ x: Number, y: Number }); Point = Type(Number);", 1)] + [InlineData("Point := Type({ x: Number, y: Number }); Point := Type(Number);", 1)] // Redeclare typed name in record - [InlineData("X= Type({ f:Number, f:Number});", 0)] + [InlineData("X:= Type({ f:Number, f:Number});", 0)] // Cyclic definition - [InlineData("B = Type({ x: A }); A = Type(B);", 0)] - [InlineData("B = Type(B);", 0)] + [InlineData("B := Type({ x: A }); A := Type(B);", 0)] + [InlineData("B := Type(B);", 0)] // Complex resolutions - [InlineData("C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type(Number);", 3)] - [InlineData("D = Type({nArray: [Number]}), C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type([C]);", 1)] + [InlineData("C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type(Number);", 3)] + [InlineData("D := Type({nArray: [Number]}), C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type([C]);", 1)] // With Invalid types - [InlineData("A = Type(Blob); B = Type({x: Currency}); C = Type([DateTime]); D = Type(None)", 2)] + [InlineData("A := Type(Blob); B := Type({x: Currency}); C := Type([DateTime]); D := Type(None)", 2)] // Have named formulas and udf in the script - [InlineData("NAlias = Type(Number);X = 5; ADDX(n:Number): Number = n + X; SomeType = Type(UntypedObject)", 2)] + [InlineData("NAlias := Type(Number);X := 5; ADDX(n:Number): Number = n + X; SomeType := Type(UntypedObject)", 2)] public void TestValidUDTCounts(string typeDefinition, int expectedDefinedTypesCount) { var parseOptions = new ParserOptions @@ -118,11 +118,13 @@ public void TestValidUDTCounts(string typeDefinition, int expectedDefinedTypesCo [Theory] //To test DefinitionsCheckResult.ApplyErrors method and error messages - [InlineData("Point = Type({ x: Number, y: Number }); Point = Type(Number);", 1, "ErrNamedType_TypeAlreadyDefined")] - [InlineData("X= Type({ f:Number, f:Number});", 1, "ErrNamedType_InvalidTypeDefinition")] - [InlineData("B = Type({ x: A }); A = Type(B);", 2, "ErrNamedType_InvalidCycles")] - [InlineData("B = Type(B);", 1, "ErrNamedType_InvalidCycles")] - [InlineData("Currency = Type({x: Text}); Record = Type([DateTime]); D = Type(None);", 2, "ErrNamedType_InvalidTypeName")] + [InlineData("Point := Type({ x: Number, y: Number }); Point := Type(Number);", 1, "ErrNamedType_TypeAlreadyDefined")] + [InlineData("X:= Type({ f:Number, f:Number});", 1, "ErrNamedType_InvalidTypeDefinition")] + [InlineData("B := Type({ x: A }); A := Type(B);", 2, "ErrNamedType_InvalidCycles")] + [InlineData("B := Type(B);", 1, "ErrNamedType_InvalidCycles")] + [InlineData("Currency := Type({x: Text}); Record := Type([DateTime]); D := Type(None);", 2, "ErrNamedType_InvalidTypeName")] + [InlineData("A = 5;C :=; B := Type(Number);", 1, "ErrNamedType_MissingTypeLiteral")] + [InlineData("C := 5; D := [1,2,3];", 2, "ErrNamedType_MissingTypeLiteral")] public void TestUDTErrors(string typeDefinition, int expectedErrorCount, string expectedMessageKey) { var parseOptions = new ParserOptions @@ -140,21 +142,21 @@ public void TestUDTErrors(string typeDefinition, int expectedErrorCount, string } [Theory] - [InlineData("T = Type({ x: 5+5, y: -5 });", 2)] - [InlineData("T = Type(Type(Number));", 1)] - [InlineData("T = Type({+});", 1)] - [InlineData("T = Type({);", 1)] - [InlineData("T = Type({x: true, y: \"Number\"});", 2)] - [InlineData("T1 = Type({A: Number}); T2 = Type(T1.A);", 1)] - [InlineData("T = Type((1, 2));", 1)] - [InlineData("T1 = Type(UniChar(955)); T2 = Type([Table(Number)])", 2)] - [InlineData("T = Type((1; 2));", 1)] - [InlineData("T = Type(Self.T);", 1)] - [InlineData("T = Type(Parent.T);", 1)] - [InlineData("T = Type(Number As T1);", 1)] - [InlineData("T = Type(Text); T1 = Type(Not T);", 1)] - [InlineData("T1 = Type({V: Number}); T2 = Type(T1[@V]);", 1)] - [InlineData("T = Type([{a: {b: {c: [{d: 10e+4}]}}}]);", 1)] + [InlineData("T := Type({ x: 5+5, y: -5 });", 2)] + [InlineData("T := Type(Type(Number));", 1)] + [InlineData("T := Type({+});", 1)] + [InlineData("T := Type({);", 1)] + [InlineData("T := Type({x: true, y: \"Number\"});", 2)] + [InlineData("T1 := Type({A: Number}); T2 := Type(T1.A);", 1)] + [InlineData("T := Type((1, 2));", 1)] + [InlineData("T1 := Type(UniChar(955)); T2 := Type([Table(Number)])", 2)] + [InlineData("T := Type((1; 2));", 1)] + [InlineData("T := Type(Self.T);", 1)] + [InlineData("T := Type(Parent.T);", 1)] + [InlineData("T := Type(Number As T1);", 1)] + [InlineData("T := Type(Text); T1 := Type(Not T);", 1)] + [InlineData("T1 := Type({V: Number}); T2 := Type(T1[@V]);", 1)] + [InlineData("T := Type([{a: {b: {c: [{d: 10e+4}]}}}]);", 1)] public void TestUDTParseErrors(string typeDefinition, int expectedErrorCount) { var parseOptions = new ParserOptions diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs index 524433dffa..ed8850dbe4 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/FileExpressionEvaluationTests.cs @@ -43,7 +43,8 @@ public void Canvas_Float(ExpressionTestCase t) var features = new Features() { TableSyntaxDoesntWrapRecords = true, - ConsistentOneColumnTableResult = true + ConsistentOneColumnTableResult = true, + IsUserDefinedTypesEnabled = true, }; RunExpressionTestCase(t, features, numberIsFloat: true, Console); @@ -60,6 +61,7 @@ public void Canvas_Float_PFxV1(ExpressionTestCase t) TableSyntaxDoesntWrapRecords = true, ConsistentOneColumnTableResult = true, PowerFxV1CompatibilityRules = true, + IsUserDefinedTypesEnabled = true, }; RunExpressionTestCase(t, features, numberIsFloat: true, Console); diff --git a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs index b681966a19..9f9a3b16b5 100644 --- a/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs +++ b/src/tests/Microsoft.PowerFx.Interpreter.Tests.Shared/RecalcEngineTests.cs @@ -617,7 +617,7 @@ public void ShadowingFunctionPrecedenceTest() result = check.GetEvaluator().Eval(); Assert.Equal(11111, result.AsDouble()); - engine.AddUserDefinitions("Test = Type({A: Number}); TestTable = Type([{A: Number}]);" + + engine.AddUserDefinitions("Test := Type({A: Number}); TestTable := Type([{A: Number}]);" + "Filter(X: TestTable):Test = First(X); ShowColumns(X: TestTable):TestTable = FirstN(X, 3);"); check = engine.Check("Filter([{A: 123}]).A"); @@ -634,7 +634,39 @@ public void ShadowingFunctionPrecedenceTest() result = check.GetEvaluator().Eval(); Assert.Equal(3, result.AsDouble()); } + + [Theory] + + // Behavior function in non-imperative udf + [InlineData( + "TestFunc():Void = Set(a, 123);", + true, + "Behavior function in a non-behavior user-defined function", + false)] + + // Behavior function in imperative udf + [InlineData( + "TestFunc():Void = { Set(a, 123); };", + false, + null, + true)] + public void BehaviorFunctionInImperativeUDF(string udfExpression, bool expectedError, string expectedErrorKey, bool allowSideEffects) + { + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + engine.UpdateVariable("a", 1m); + + var result = engine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: allowSideEffects); + Assert.True(expectedError ? result.Errors.Count() > 0 : result.Errors.Count() == 0); + + if (expectedError) + { + result.Errors.Any(error => error.MessageKey == expectedErrorKey); + } + } + [Theory] // Return value with side effectful UDF @@ -679,6 +711,39 @@ public void ImperativeUserDefinedFunctionTest(string udfExpression, string expre } } + [Theory] + + [InlineData( + "MismatchType():Number = { 1+3; Color.Blue; };", + true, + true, + 36, + 41)] + [InlineData( + "MatchType():Text = { 4; 3 };", + false, + true, + 0, + 0)] + public void TestMismatchReturnType(string udfExpression, bool expectedError, bool allowSideEffects, int min, int lim) + { + var config = new PowerFxConfig(); + config.EnableSetFunction(); + var engine = new RecalcEngine(config); + engine.UpdateVariable("x", 1m); + + var result = engine.AddUserDefinedFunction(udfExpression, CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: allowSideEffects); + Assert.True(expectedError ? result.Errors.Count() > 0 : result.Errors.Count() == 0); + + if (expectedError) + { + var error = result.Errors.First(error => error.MessageKey == "ErrUDF_ReturnTypeDoesNotMatch"); + Assert.NotNull(error); + var span = error.Span; + Assert.True(span.Min == min && span.Lim == lim); + } + } + [Fact] public void DelegableUDFTest() @@ -733,6 +798,43 @@ public void DelegableUDFTest() // Binding fails for recursive definitions and hence function is not added. Assert.False(recalcEngine.AddUserDefinedFunction("E():Void = { E(); };", CultureInfo.InvariantCulture, symbolTable: recalcEngine.EngineSymbols, allowSideEffects: true).IsSuccess); + } + + [Fact] + public void TestInheritanceOfDelegationWarningsInUDFs() + { + var symbolTable = new DelegatableSymbolTable(); + var schema = DType.CreateTable( + new TypedName(DType.Number, new DName("Value"))); + symbolTable.AddEntity(new TestDelegableDataSource( + "MyDataSource", + schema, + new TestDelegationMetadata( + new DelegationCapability(DelegationCapability.Filter), + schema, + new FilterOpMetadata( + schema, + new Dictionary(), + new Dictionary(), + new DelegationCapability(DelegationCapability.GreaterThan), + null)), + true)); + symbolTable.AddType(new DName("MyDataSourceTableType"), FormulaType.Build(schema)); + var config = new PowerFxConfig() + { + SymbolTable = symbolTable + }; + var engine = new RecalcEngine(config); + + var result = engine.AddUserDefinedFunction("NonDelegatableUDF():MyDataSourceTableType = Filter(MyDataSource, Sqrt(Value) > 5);", CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: true); + Assert.True(result.IsSuccess); + var func = engine.Functions.WithName("NonDelegatableUDF").First() as UserDefinedFunction; + Assert.True(func.HasDelegationWarning); + + result = engine.AddUserDefinedFunction("NonDelegatableUDF2():MyDataSourceTableType = NonDelegatableUDF();", CultureInfo.InvariantCulture, symbolTable: engine.EngineSymbols, allowSideEffects: true); + Assert.True(result.IsSuccess); + func = engine.Functions.WithName("NonDelegatableUDF2").First() as UserDefinedFunction; + Assert.True(func.HasDelegationWarning); } // Binding to inner functions does not impact outer functions. @@ -1651,57 +1753,57 @@ public void LookupBuiltinOptionSets() [Theory] [InlineData( - "Point = Type({x : Number, y : Number}); distance(a: Point, b: Point): Number = Sqrt(Power(b.x-a.x, 2) + Power(b.y-a.y, 2));", + "Point := Type({x : Number, y : Number}); distance(a: Point, b: Point): Number = Sqrt(Power(b.x-a.x, 2) + Power(b.y-a.y, 2));", "distance({x: 0, y: 0}, {x: 0, y: 5})", true, 5.0)] // Table types are accepted [InlineData( - "People = Type([{Id:Number, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", + "People := Type([{Id:Number, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", "countMinors([{Id: 1, Age: 17}, {Id: 2, Age: 21}])", true, 1.0)] [InlineData( - "Numbers = Type([Number]); countEven(nums: Numbers): Number = CountRows(Filter(nums, Mod(Value, 2) = 0));", + "Numbers := Type([Number]); countEven(nums: Numbers): Number = CountRows(Filter(nums, Mod(Value, 2) = 0));", "countEven([1,2,3,4,5,6,7,8,9,10])", true, 5.0)] // Type Aliases are allowed [InlineData( - "CarYear = Type(Number); Car = Type({Model: Text, ModelYear: CarYear}); createCar(model:Number, year: Number): Car = {Model:model, ModelYear: year};", + "CarYear := Type(Number); Car := Type({Model: Text, ModelYear: CarYear}); createCar(model:Number, year: Number): Car = {Model:model, ModelYear: year};", "createCar(\"Model Y\", 2024).ModelYear", true, 2024.0)] // Type definitions order shouldn't matter [InlineData( - "Person = Type({Id: IdType, Age: Number}); IdType = Type(Number); createUser(id:Number, a: Number): Person = {Id:id, Age: a};", + "Person := Type({Id: IdType, Age: Number}); IdType := Type(Number); createUser(id:Number, a: Number): Person = {Id:id, Age: a};", "createUser(1, 42).Age", true, 42.0)] // Functions accept record with more/less fields [InlineData( - "People = Type([{Name: Text, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", + "People := Type([{Name: Text, Age: Number}]); countMinors(p: People): Number = CountRows(Filter(p, Age < 18));", "countMinors([{Name: \"Bob\", Age: 21, Title: \"Engineer\"}, {Name: \"Alice\", Age: 25, Title: \"Manager\"}])", true, 0.0)] [InlineData( - "Employee = Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age;", + "Employee := Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age;", "getAge({Name: \"Bob\", Age: 21})", true, 21.0)] [InlineData( - @"Employee = Type({Name: Text, Age: Number, Title: Text}); Employees = Type([Employee]); EmployeeNames = Type([{Name: Text}]); + @"Employee := Type({Name: Text, Age: Number, Title: Text}); Employees := Type([Employee]); EmployeeNames := Type([{Name: Text}]); getNames(e: Employees):EmployeeNames = ShowColumns(e, Name); getNamesCount(e: EmployeeNames):Number = CountRows(getNames(e));", "getNamesCount([{Name: \"Jim\", Age:25}, {Name: \"Tony\", Age:42}])", true, 2.0)] [InlineData( - @"Employee = Type({Name: Text, Age: Number, Title: Text}); + @"Employee := Type({Name: Text, Age: Number, Title: Text}); getAge(e: Employee): Number = e.Age; hasNoAge(e: Employee): Number = IsBlank(getAge(e));", "hasNoAge({Name: \"Bob\", Title: \"CEO\"})", @@ -1710,8 +1812,8 @@ public void LookupBuiltinOptionSets() // Types with UDF restricted primitive types resolve successfully [InlineData( - @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); - Patients = Type([Patient]); + @"Patient := Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); + Patients := Type([Patient]); Dummy():Number = CountRows([]);", "Dummy()", true, @@ -1719,45 +1821,45 @@ public void LookupBuiltinOptionSets() // Aggregate types with restricted types are not allowed in UDF [InlineData( - @"Patient = Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); - Patients = Type([Patient]); + @"Patient := Type({DOB: DateTimeTZInd, Weight: Decimal, Dummy: None}); + Patients := Type([Patient]); getAnomaly(p: Patients): Patients = Filter(p, Weight < 0);", "", false)] [InlineData( - @"Patient = Type({Name: Text, Details: {h: Number, w:Decimal}}); + @"Patient := Type({Name: Text, Details: {h: Number, w:Decimal}}); getPatient(): Patient = {Name:""Alice"", Details: {h: 1, w: 2}};", "", false)] // Cycles not allowed [InlineData( - "Z = Type([{a: {b: Z}}]);", + "Z := Type([{a: {b: Z}}]);", "", false)] [InlineData( - "X = Type(Y); Y = Type(X);", + "X := Type(Y); Y := Type(X);", "", false)] [InlineData( - "C = Type({x: Boolean, y: Date, f: B});B = Type({ x: A }); A = Type([C]);", + "C := Type({x: Boolean, y: Date, f: B});B := Type({ x: A }); A := Type([C]);", "", false)] // Redeclaration not allowed [InlineData( - "Number = Type(Text);", + "Number := Type(Text);", "", false)] [InlineData( - "Point = Type({x : Number, y : Number}); Point = Type({x : Number, y : Number, z: Number})", + "Point := Type({x : Number, y : Number}); Point := Type({x : Number, y : Number, z: Number})", "", false)] // UDFs with body errors should fail [InlineData( - "S = Type({x:Text}); f():S = ({);", + "S := Type({x:Text}); f():S = ({);", "", false)] @@ -1776,7 +1878,7 @@ public void UserDefinedTypeTest(string userDefinitions, string evalExpression, b var parserOptions = new ParserOptions() { AllowsSideEffects = false, - AllowParseAsTypeLiteral = true + AllowParseAsTypeLiteral = true, }; if (isValid) diff --git a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs index 5b74f1c72e..57a56ccdd0 100644 --- a/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs +++ b/src/tests/Microsoft.PowerFx.Json.Tests.Shared/AsTypeIsTypeParseJSONTests.cs @@ -26,10 +26,11 @@ public class AsTypeIsTypeParseJSONTests AllowParseAsTypeLiteral = true, }; - private RecalcEngine SetupEngine() + private RecalcEngine SetupEngine(bool udtFeaturedEnabled = true) { var config = new PowerFxConfig(); config.EnableJsonFunctions(); + config.Features.IsUserDefinedTypesEnabled = udtFeaturedEnabled; return new RecalcEngine(config); } @@ -39,7 +40,7 @@ public void PrimitivesTest() var engine = SetupEngine(); // custom-type type alias - engine.AddUserDefinitions("T = Type(Number);"); + engine.AddUserDefinitions("T := Type(Number);"); // Positive tests CheckIsTypeAsTypeParseJSON(engine, "\"42\"", "Number", 42D); @@ -74,7 +75,7 @@ public void RecordsTest() { var engine = SetupEngine(); - engine.AddUserDefinitions("T = Type({a: Number});"); + engine.AddUserDefinitions("T := Type({a: Number});"); dynamic obj1 = new ExpandoObject(); obj1.a = 5D; @@ -103,7 +104,7 @@ public void TablesTest() { var engine = SetupEngine(); - engine.AddUserDefinitions("T = Type([{a: Number}]);"); + engine.AddUserDefinitions("T := Type([{a: Number}]);"); var t1 = new object[] { 5D }; var t2 = new object[] { 1m, 2m, 3m, 4m }; @@ -148,6 +149,18 @@ public void TestCompileErrors(string expression, string type, bool testAllFuncti } } + [Theory] + [InlineData("AsType(ParseJSON(\"123\"), Number)")] + [InlineData("IsType(ParseJSON(\"123\"), Type(Number))")] + [InlineData("ParseJSON(\"\"\"Hello\"\"\", Type(Text))")] + public void TestCompileErrorsWithUDTFeatureDisabled(string expression) + { + var engine = SetupEngine(udtFeaturedEnabled: false); + var result = engine.Check(expression); + Assert.False(result.IsSuccess); + Assert.Contains(result.Errors, e => e.MessageKey == "ErrUserDefinedTypesDisabled"); + } + [Fact] public void TestFunctionsWithTypeArgs() { diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.projitems b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.projitems index d40b69acef..71b973926e 100644 --- a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.projitems +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Microsoft.PowerFx.Repl.Tests.Shared.projitems @@ -10,8 +10,20 @@ + + + + + + + + + + + Always + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Mocks/TempFileHolder.cs b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Mocks/TempFileHolder.cs new file mode 100644 index 0000000000..e84793a922 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Mocks/TempFileHolder.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.IO; + +namespace Microsoft.PowerFx.Repl.Tests +{ + // Helper for creating temp files and cleaning up. + internal class TempFileHolder : IDisposable + { + public string FullPath { get; } = Path.GetTempFileName(); + + public void Dispose() + { + // Cleanup the file. + try + { + File.Delete(this.FullPath); + } + catch + { + // Ignore + } + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ModuleIdentityTests.cs b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ModuleIdentityTests.cs new file mode 100644 index 0000000000..0c3c9be011 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ModuleIdentityTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.PowerFx.Repl.Tests +{ + public class ModuleIdentityTests + { + [Fact] + public void Collections() + { + var id1a = ModuleIdentity.FromFile(@"z:\path1.txt"); + var id1b = ModuleIdentity.FromFile(@"z:\Path1.txt"); + + var id2 = ModuleIdentity.FromFile(@"z:\path2.txt"); + + Assert.Equal(id1a, id1a); // identity + Assert.Equal(id1a.GetHashCode(), id1a.GetHashCode()); + + Assert.NotEqual(id1a, id2); + + // paths should be normalized + Assert.Equal(id1a, id1b); + + // Work with collections. + var set = new HashSet(); + + var added = set.Add(id1a); + Assert.True(added); + + added = set.Add(id1a); + Assert.False(added); + + added = set.Add(id1b); + Assert.False(added); + + Assert.Contains(id1a, set); + Assert.Contains(id1b, set); + + Assert.DoesNotContain(id2, set); + + added = set.Add(id2); + Assert.True(added); + } + + // Ensure we normalize file paths, particularly when using ".." + [Fact] + public void Canonical() + { + var id1a = ModuleIdentity.FromFile(@"z:\foo\path1.txt"); + var id1b = ModuleIdentity.FromFile(@"z:\foo\bar\..\Path1.txt"); + + Assert.Equal(id1a, id1a); // identity + } + + [Fact] + public void NoToString() + { + var id1 = ModuleIdentity.FromFile(@"z:\foo\path1.txt"); + + // *Don't implement ToString* - we want to treat identity as opauque. + var str = id1.ToString(); + + Assert.Equal("Microsoft.PowerFx.Repl.ModuleIdentity", str); + } + + [Fact] + public void MustBeFullPath() + { + Assert.Throws(() => ModuleIdentity.FromFile(null)); + + // Must be a full path + Assert.Throws(() => ModuleIdentity.FromFile("path1.txt")); + Assert.Throws(() => ModuleIdentity.FromFile(@".\path1.txt")); + Assert.Throws(() => ModuleIdentity.FromFile(string.Empty)); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ModuleTests.cs b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ModuleTests.cs new file mode 100644 index 0000000000..7d7c438cf2 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/ModuleTests.cs @@ -0,0 +1,306 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.PowerFx.Types; +using Xunit; + +#pragma warning disable CS0618 // Type or member is obsolete +#pragma warning disable SA1118 // Parameter should not span multiple lines + +namespace Microsoft.PowerFx.Repl.Tests +{ + // Thesde tests should target public Module Import() function + // (and not internal services) + public class ModuleTests + { + private PowerFxREPL _repl; + private readonly TestReplOutput _output = new TestReplOutput(); + + public ModuleTests() + { + var config = new PowerFxConfig(); + config.SymbolTable.EnableMutationFunctions(); + + // config.EnableSetFunction(); + var engine = new RecalcEngine(config); + + _repl = new PowerFxREPL + { + Engine = engine, + Output = _output, + AllowSetDefinitions = true, + AllowUserDefinedFunctions = true, + AllowImport = true, + ParserOptions = new ParserOptions() { AllowsSideEffects = true } + }; + } + + // Run line, return the normal output. + private string HandleLine(string fx, bool expectErrors = false) + { + Debugger.Log(0, string.Empty, ">> " + fx); + _repl.HandleLine(fx); + + if (!expectErrors) + { + AssertNoErrors(); + } + + var log = _output.Get(OutputKind.Repl); + + Debugger.Log(0, string.Empty, log); + return log; + } + + private void AssertNoErrors() + { + var errors = _output.Get(OutputKind.Error); + Assert.Empty(errors); + } + + private string GetModuleFullPath(string path) + { + string fullpath = Path.Combine( + Environment.CurrentDirectory, + "Modules", + path); + + return fullpath; + } + + private void Import(string path, bool expectErrors = false) + { + string fullpath = GetModuleFullPath(path); + + var expr = $"Import(\"{fullpath}\")"; + HandleLine(expr, expectErrors); + + if (expectErrors) + { + var modules = _repl.Modules.ToArray(); + Assert.Empty(modules); // nothing actually loaded. + } + } + + // Ensure that we only can access modules if they're enabled. + [Fact] + public void MustEnable() + { + var engine = new RecalcEngine(); + + _repl = new PowerFxREPL + { + Engine = engine, + Output = _output + }; + + // Defaults to false + Assert.False(_repl.AllowUserDefinedFunctions); + + Import("basic1.fx.yml", expectErrors: true); + + // Failed to import. + var msg = _output.Get(OutputKind.Error); + Assert.Contains("'Import' is an unknown or unsupported function.", msg); + } + + [Fact] + public void Test1() + { + Import("basic1.fx.yml"); + + // Call method defined in moduel. + var log = HandleLine("DoubleIt(3)"); + + Assert.Equal("6", log); + } + + // Load multiple modules + [Fact] + public void Test2() + { + Import("basic1.fx.yml"); + Import("basic2.fx.yml"); + + // Call method defined in moduel. + var log = HandleLine("DoubleIt(Add1(3))"); + + Assert.Equal("8", log); + } + + // Load with imports + [Fact] + public void TestImport() + { + Import("Depend2.fx.yml"); + + // Call method defined in moduel. + var log = HandleLine("Func2(3)"); + + Assert.Equal("8", log); + } + + // Load with imports + [Fact] + public void TestImport2() + { + Import("Depend2.fx.yml"); + + // Call method defined in moduel. + var log = HandleLine("Func2(3)"); + Assert.Equal("8", log); + + // Inner dependencies are not exported + log = HandleLine("Add1(3)", expectErrors: true); + var msg = _output.Get(OutputKind.Error); + Assert.Contains("Add1' is an unknown or unsupported function.", msg); + + // But we can import the inner module and call. + Import("basic2.fx.yml"); + log = HandleLine("Add1(3)"); + Assert.Equal("4", log); + } + + // Load with imports + [Fact] + public void Recursion() + { + Import("recursion.fx.yml"); + + // 0,1, 1, 2, 3, 5, 8,13 + var log = HandleLine("Fib(7)"); + Assert.Equal("13", log); + } + + // Load with imports + [Fact] + public void Shadow() + { + Import("shadow.fx.yml"); + + var log = HandleLine("Foo()"); + Assert.Equal("-2", log); + + log = HandleLine("Abs(-7)"); // calls new one from module + } + + // Diamond inheritence. + // 1 --> {2a, 2b}. 2a-->3. 2b-->3. + // This is interesting since module 3 gets used multiple times, but it's not a cycle. + [Fact] + public void Diamond() + { + Import("diamond_1.fx.yml"); + + var log = HandleLine("Func1(5)"); + Assert.Equal("\"2A(3(5)),2B(3(5))\"", log); + } + + // Conflict if we import 2 modules that define the same symbols. + [Fact] + public void DuplicateSymbolsConflict() + { + Import("conflict1.fx.yml", expectErrors: true); + var errorMsg = _output.Get(OutputKind.Error); + + // message contains useful information. + Assert.Contains("DoubleIt", errorMsg); // conflicting symbol + Assert.Contains("basic1.fx.yml", errorMsg); // module 1 + Assert.Contains("basic1_dup.fx.yml", errorMsg); // module 2 + } + + // Import() is a meta function and can only be called + // at top-levle repl and not within a module itself. + [Fact] + public void ImportIsAMetafunction() + { + Import("ErrorImport.fx.yml", expectErrors: true); + var errorMsg = _output.Get(OutputKind.Error); + + Assert.Contains("'Import' is an unknown or unsupported function", errorMsg); + } + + // When we import a file twice, we get the updated contents. + [Fact] + public void GetsLatest() + { + using var temp = new TempFileHolder(); + string fullpath = temp.FullPath; + + // First version + File.WriteAllText(fullpath, @" +Formulas: | + Func(x: Number) : Number = x * 10; +"); + HandleLine($"Import(\"{fullpath}\")"); + + // Call method defined in moduel. + var before = HandleLine("Func(3)"); + Assert.Equal("30", before); + + // Update the file, 2nd version + File.WriteAllText(fullpath, @" +Formulas: | + Func(x: Number) : Number = x * 20; +"); + + HandleLine($"Import(\"{fullpath}\")"); + + var after = HandleLine("Func(3)"); + Assert.Equal("60", after); + } + + // Error gets spans. + [Fact] + public void ErrorShowsFileRange() + { + Import("Error1.fx.yml", expectErrors: true); + + var errorMsg = _output.Get(OutputKind.Error); + + // Key elements here are that: + // - the message has the filename + // - the location (5,9) is relative into the file, not just the expression. + Assert.Equal("Error: Error1.fx.yml (5,9): Name isn't valid. 'missing' isn't recognized.", errorMsg); + } + + // Circular references are detected + [Fact] + public void Cycles() + { + Import("cycle1.fx.yml", expectErrors: true); + + var errorMsg = _output.Get(OutputKind.Error); + Assert.Contains("Circular reference", errorMsg); + } + + // Loading a missing file + [Fact] + public void Missing() + { + Import("missing_file.fx.yml", expectErrors: true); + + var errorMsg = _output.Get(OutputKind.Error); + Assert.Contains("missing_file.fx.yml", errorMsg); + } + + // Loading a file with yaml parse errors. + [Fact] + public void YamlParseErrors() + { + Import("yaml_parse_errors.fx.yml", expectErrors: true); + + var errorMsg = _output.Get(OutputKind.Error); + } + } +} diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/Depend2.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/Depend2.fx.yml new file mode 100644 index 0000000000..bb1769d6b2 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/Depend2.fx.yml @@ -0,0 +1,7 @@ +Imports: +- File: Basic1.fx.yml +- File: Basic2.fx.yml + +# Depends on Basic1 +Formulas: | + Func2(x: Number) : Number = DoubleIt(Add1(x)); diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/Error1.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/Error1.fx.yml new file mode 100644 index 0000000000..14d82011b4 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/Error1.fx.yml @@ -0,0 +1,6 @@ +# Has an error in expression. +# Test that error reporting gets right line. +Formulas: | + DoubleIt(x: Number) : Number = + missing // <-- error is on line 5 + * 2; diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/ErrorImport.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/ErrorImport.fx.yml new file mode 100644 index 0000000000..ad78e7094c --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/ErrorImport.fx.yml @@ -0,0 +1,7 @@ +# Error - we can't call Import() within a function. +# must be a top-level yaml construct. + +Formulas: | + Func2(x: Number) : Void = { + Import("Basic1.fx.yml") + }; diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic1.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic1.fx.yml new file mode 100644 index 0000000000..e7913abd11 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic1.fx.yml @@ -0,0 +1,3 @@ +# Most basic Power Fx module. +Formulas: | + DoubleIt(x: Number) : Number = x * 2; diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic1_dup.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic1_dup.fx.yml new file mode 100644 index 0000000000..73e2c514bb --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic1_dup.fx.yml @@ -0,0 +1,4 @@ +# test symbol conflicts. +# Same symbols as base1.fx.yml +Formulas: | + DoubleIt(x: Number) : Number = x * 200; \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic2.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic2.fx.yml new file mode 100644 index 0000000000..78aeb00d3f --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/basic2.fx.yml @@ -0,0 +1,3 @@ +# Most basic Power Fx module. +Formulas: | + Add1(x: Number) : Number = x +1; diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/conflict1.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/conflict1.fx.yml new file mode 100644 index 0000000000..bedea06a71 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/conflict1.fx.yml @@ -0,0 +1,6 @@ +Imports: +- File: basic1.fx.yml +- File: basic1_dup.fx.yml + +Formulas: | + Run() : Number = DoubleIt(5); \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/cycle1.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/cycle1.fx.yml new file mode 100644 index 0000000000..7395675eb4 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/cycle1.fx.yml @@ -0,0 +1,6 @@ +# Test circular dependency +Imports: +- File: cycle2.fx.yml + +Formulas: | + Func1(x: Number) : Number = 1; diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/cycle2.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/cycle2.fx.yml new file mode 100644 index 0000000000..bb48463c90 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/cycle2.fx.yml @@ -0,0 +1,6 @@ +# Test circular dependency +Imports: +- File: cycle1.fx.yml + +Formulas: | + Func2(x: Number) : Number = 1; diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_1.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_1.fx.yml new file mode 100644 index 0000000000..a5d90a235b --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_1.fx.yml @@ -0,0 +1,7 @@ +Imports: +- File: diamond_2a.fx.yml +- File: diamond_2b.fx.yml + +# Depends on 2a,2b. Both depend on 3. +Formulas: | + Func1(x: Text) : Text = Func2a(x) & "," & Func2b(x); diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_2a.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_2a.fx.yml new file mode 100644 index 0000000000..813a91085d --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_2a.fx.yml @@ -0,0 +1,8 @@ +Imports: +- File: diamond_3.fx.yml + + +# Depends on Basic1 +Formulas: | + Func2a(x: Text) : Text = $"2A({Func3(x)})"; + diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_2b.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_2b.fx.yml new file mode 100644 index 0000000000..aa65f242b0 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_2b.fx.yml @@ -0,0 +1,8 @@ +Imports: +- File: diamond_3.fx.yml + + +# Depends on Basic1 +Formulas: | + Func2b(x: Text) : Text = $"2B({Func3(x)})"; + diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_3.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_3.fx.yml new file mode 100644 index 0000000000..454ce67593 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/diamond_3.fx.yml @@ -0,0 +1,3 @@ + +Formulas: | + Func3(x: Text) : Text = $"3({x})"; \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/recursion.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/recursion.fx.yml new file mode 100644 index 0000000000..5b0d6c0a93 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/recursion.fx.yml @@ -0,0 +1,4 @@ +# Define a basic recursive function. +Formulas: | + Fib(n: Number) : Number = + If(n <= 1, n, Fib(n - 1) + Fib(n - 2)); diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/shadow.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/shadow.fx.yml new file mode 100644 index 0000000000..3782bf116b --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/shadow.fx.yml @@ -0,0 +1,8 @@ + +Formulas: | + // Redefine a builtin. + // Give a different (wrong) implementation so we know which is called. + Abs(x: Number) : Number = x *2; + + // Binds to the one in this scope. + Foo() : Number = Abs(-1); \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/yaml_parse_errors.fx.yml b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/yaml_parse_errors.fx.yml new file mode 100644 index 0000000000..9aa6d975f5 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/Modules/yaml_parse_errors.fx.yml @@ -0,0 +1,3 @@ +# not a valid yaml file +Formulas:: + \ No newline at end of file diff --git a/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/StringWithSourceTests.cs b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/StringWithSourceTests.cs new file mode 100644 index 0000000000..604de09bc3 --- /dev/null +++ b/src/tests/Microsoft.PowerFx.Repl.Tests.Shared/StringWithSourceTests.cs @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; +using Microsoft.PowerFx.Types; +using Xunit; +using YamlDotNet.Core; +using YamlDotNet.Serialization; + +namespace Microsoft.PowerFx.Repl.Tests +{ +#pragma warning disable SA1117 // Parameters should be on same line or separate lines + + public class StringWithSourceTests + { + private class Poco1 + { + public StringWithSource Prop { get; set; } + } + + private class Poco1Direct + { + public string Prop { get; set; } + } + + // common fake filename used in locations. + private const string Filename = "myfile1.txt"; + + private T Parse(string contents) + { + // Deserialize. + var deserializer = new DeserializerBuilder() + .WithTypeConverter(new StringWithSourceConverter(Filename, contents)) + .Build(); + + var poco = deserializer.Deserialize(contents); + + return poco; + } + + private T ParseExpectError(string contents, string expectedError) + { + try + { + // Deserialize. + var deserializer = new DeserializerBuilder() + .WithTypeConverter(new StringWithSourceConverter(Filename, contents)) + .Build(); + + var poco = deserializer.Deserialize(contents); + + Assert.Fail($"Expected error: {expectedError}"); + return poco; + } + catch (YamlException e) + { + // Exceptions from parser will be wrapped in YamlException. + var msg = e?.InnerException?.Message ?? e.Message; + Assert.Contains(expectedError, msg); + } + + return default; + } + + // In all cases, parse as 'string' should succeed. + // But Parse with SourceLocaiton has more limitations. + // expectedError specifies restrictions. + private StringWithSource Test(string contents, string expectedError = null) + { + // Should always work with 'string' + var p1 = Parse(contents); + + if (expectedError != null) + { + ParseExpectError(contents, expectedError); + return null; + } + else + { + var p2 = Parse(contents); + + Assert.Equal(p1.Prop, p2.Prop.Value); + Assert.Same(Filename, p2.Prop.Location.Filename); + + if (p2.Prop.Value != null) + { + p2.Prop.Value = p2.Prop.Value.Replace("\r", string.Empty); + } + + return p2.Prop; + } + } + + [Theory] + [InlineData( +@"Prop: | + 123 +", 2, 3, "123\n")] + + // Leading space in front of property. + [InlineData( +@" Prop: | + 123 +", 2, 3, "123\n")] + + [InlineData( +@"# another line +Prop: | + 123 + 456", 3, 6, "123\n456")] + + [InlineData( +@"Prop: |- + 123 +", 2, 3, "123")] + + [InlineData( +@"Prop: |- + true +", 2, 3, "true")] // YDN will still return as string. + + // Other encodings (not | ) are not supported. + [InlineData( +@"Prop: > + 123 + 456 +", 0, 0, "123456\n", "literal")] // Folding + + [InlineData( +@"Prop: 123456 +", 0, 0, "123456", "literal")] // plain + + [InlineData( +@"Prop: ""123456"" +", 0, 0, "123456", "literal")] // quotes + + [InlineData( +@"Prop: ", 1, 6, null)] // empty + + public void Test1(string contents, int lineStart, int colStart, string value, string errorMessage = null) + { + var p1 = Test(contents, errorMessage); + + if (errorMessage == null) + { + Assert.Equal(value, p1.Value); + Assert.Equal(colStart, p1.Location.ColStart); + Assert.Equal(lineStart, p1.Location.LineStart); + } + else + { + Assert.Null(p1); + } + } + + // Error conditions. + [Theory] + [InlineData( +"Prop: { }")] // object , expecting string. + public void TestError(string contents) + { + ParseExpectError(contents, "Failed"); + ParseExpectError(contents, "Expected 'Scalar'"); + } + } +} diff --git a/src/tools/Repl/AssertFunction.cs b/src/tools/Repl/AssertFunction.cs new file mode 100644 index 0000000000..c03b1ca043 --- /dev/null +++ b/src/tools/Repl/AssertFunction.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) PlaceholderCompany. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerFx.Types; + +#pragma warning disable CS0618 // Type or member is obsolete + +namespace Microsoft.PowerFx.Repl.Functions +{ + /// + /// Assert Function. + /// + internal class AssertFunction : ReflectionFunction + { + public AssertFunction() + : base("Assert", FormulaType.Void, new[] { FormulaType.Boolean, FormulaType.String }) + { + ConfigType = typeof(IReplOutput); + } + + public async Task Execute(IReplOutput output, BooleanValue test, StringValue message, CancellationToken cancel) + { + if (test.Value) + { + await output.WriteLineAsync($"PASSED: {message.Value}", OutputKind.Notify, cancel) + .ConfigureAwait(false); + } + else + { + await output.WriteLineAsync($"FAILED: {message.Value}", OutputKind.Error, cancel) + .ConfigureAwait(false); + } + + return FormulaValue.NewVoid(); + } + } +} diff --git a/src/tools/Repl/Program.cs b/src/tools/Repl/Program.cs index f23481dc62..045c7919ef 100644 --- a/src/tools/Repl/Program.cs +++ b/src/tools/Repl/Program.cs @@ -18,6 +18,8 @@ namespace Microsoft.PowerFx { +#pragma warning disable CS0618 // Type or member is obsolete + public static class ConsoleRepl { private const string OptionFormatTable = "FormatTable"; @@ -84,9 +86,9 @@ private static RecalcEngine ReplRecalcEngine() config.EnableSetFunction(); config.EnableJsonFunctions(); -#pragma warning disable CS0618 // Type or member is obsolete config.EnableOptionSetInfo(); -#pragma warning restore CS0618 // Type or member is obsolete + + config.AddFunction(new AssertFunction()); config.AddFunction(new ResetFunction()); config.AddFunction(new Option0Function()); @@ -97,9 +99,7 @@ private static RecalcEngine ReplRecalcEngine() var optionsSet = new OptionSet("Options", DisplayNameUtility.MakeUnique(options)); -#pragma warning disable CS0618 // Type or member is obsolete config.EnableRegExFunctions(new TimeSpan(0, 0, 5)); -#pragma warning restore CS0618 // Type or member is obsolete config.AddOptionSet(optionsSet); @@ -124,9 +124,7 @@ public static void Main() } // Hook repl engine with customizations. -#pragma warning disable CS0618 // Type or member is obsolete private class MyRepl : PowerFxREPL -#pragma warning restore CS0618 // Type or member is obsolete { public MyRepl() { @@ -138,6 +136,8 @@ public MyRepl() this.AllowSetDefinitions = true; this.AllowUserDefinedFunctions = _enableUDFs; + this.AllowImport = true; + this.EnableSampleUserObject(); this.AddPseudoFunction(new IRPseudoFunction()); this.AddPseudoFunction(new SuggestionsPseudoFunction()); @@ -427,9 +427,7 @@ public FormulaValue Execute(StringValue option, BooleanValue value) private class MyHelpProvider : HelpProvider { -#pragma warning disable CS0618 // Type or member is obsolete public override async Task Execute(PowerFxREPL repl, CancellationToken cancel, string context = null) -#pragma warning restore CS0618 // Type or member is obsolete { if (context?.ToLowerInvariant() == "options" || context?.ToLowerInvariant() == "option") {