Refactor CDP implementation and add Capabilities (#2636)

Refactor CDP implementation
Use AssociatedDataSources for CDP record types
Make all CDP records lazy
Add x-ms-enum management
New TabularRecordType (lazy)
Make CdpTableResolver return ConnectorType
Remove CdpDType hack
Remove CdpTableType hack
Remove CdpTableDescriptor
Remove ExternalCdpDataSource
Remove ICdpTableResolver.GenerateADS hack
Rework Connector service capabilities
Add Relationships to CdpService
Add TableParameters to CdpTable
New InternalTableMetadata and related classes in Pfx Core
New InternalTableParameters in Pfx Core (implementing
IExternalTabularDataSource)
Fix DType ToRecord, ToTable
This commit is contained in:
Luc Genetier 2024-10-11 23:35:28 +02:00 коммит произвёл GitHub
Родитель d3c93a3869
Коммит 0fd9e3e130
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
52 изменённых файлов: 1908 добавлений и 629 удалений

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

@ -10,6 +10,7 @@ namespace Microsoft.PowerFx.Connectors
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";

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

@ -17,6 +17,7 @@ using Microsoft.OpenApi.Models;
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 @@ namespace Microsoft.PowerFx.Connectors
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 @@ namespace Microsoft.PowerFx.Connectors
}
// Only called by ConnectorTable.GetSchema
internal static ConnectorType GetConnectorTypeAndTableCapabilities(ICdpTableResolver tableResolver, string connectorName, string valuePath, StringValue sv, List<SqlRelationship> 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<SqlRelationship> 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<OpenApiSchema>(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<ReferencedEntity> referencedEntities = GetReferenceEntities(connectorName, sv);
ISwaggerSchema tableSchema = SwaggerSchema.New(new OpenApiStringReader(oars).ReadFragment<OpenApiSchema>(stringValue.Value, OpenApi.OpenApiSpecVersion.OpenApi2_0, out OpenApiDiagnostic _));
ServiceCapabilities serviceCapabilities = tableSchema.GetTableCapabilities();
ConnectorPermission tablePermission = tableSchema.GetPermission();
bool isTableReadOnly = tablePermission == ConnectorPermission.PermissionReadOnly;
List<ConnectorType> 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<ReferencedEntity> 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;
}

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

@ -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);
}
}
}
}
}

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

@ -333,6 +333,8 @@ namespace Microsoft.PowerFx.Connectors
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 @@ namespace Microsoft.PowerFx.Connectors
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<string, IOpenApiAny> apiObj, string str) => apiObj.TryGetValue(str, out IOpenApiAny openApiAny) && openApiAny is OpenApiString openApiStr ? (true, openApiStr.Value) : (false, null);
@ -438,7 +447,8 @@ namespace Microsoft.PowerFx.Connectors
{
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 @@ namespace Microsoft.PowerFx.Connectors
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 <null>, int32, int64
case "integer":

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

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
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)
{
// We should never reach that point in CDP case
throw new System.NotImplementedException();
}
}
}

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

@ -5,7 +5,7 @@ using System;
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 @@ namespace Microsoft.PowerFx.Connectors
{
internal ConnectorType ConnectorType { get; }
internal IList<ReferencedEntity> ReferencedEntities { get; }
internal IList<SqlRelationship> SqlRelationships { get; }
internal ICdpTableResolver TableResolver { get; }
internal CdpRecordType(ConnectorType connectorType, DType recordType, ICdpTableResolver tableResolver, IList<ReferencedEntity> referencedEntities, IList<SqlRelationship> 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 @@ namespace Microsoft.PowerFx.Connectors
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 @@ namespace Microsoft.PowerFx.Connectors
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 @@ namespace Microsoft.PowerFx.Connectors
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 @@ namespace Microsoft.PowerFx.Connectors
public override string TableSymbolName => ConnectorType.Name;
public override IEnumerable<string> FieldNames => _type.GetRootFieldNames().Select(name => name.Value);
public override IEnumerable<string> FieldNames => ConnectorType.Fields.Select(field => field.Name);
}
}

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

@ -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();
}
}
}

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

@ -6,7 +6,7 @@ using System.Collections.Generic;
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 @@ namespace Microsoft.PowerFx.Connectors
{
public bool IsDelegable => _tabularService.IsDelegable;
protected internal readonly CdpService _tabularService;
protected internal readonly CdpService _tabularService;
protected internal readonly ConnectorType _connectorType;
internal readonly IReadOnlyDictionary<string, Relationship> Relationships;
private IReadOnlyCollection<DValue<RecordValue>> _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<string, Relationship> relationships)
: base(IRContext.NotInSource(tabularService.TableType))
{
_tabularService = tabularService;
_connectorType = connectorType;
Relationships = relationships;
HttpClient = tabularService.HttpClient;
}

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

@ -8,10 +8,8 @@ using System.Linq;
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 @@ namespace Microsoft.PowerFx.Connectors
{
}
internal ConnectorType(JsonElement schema, ConnectorCompatibility compatibility, IList<SqlRelationship> sqlRelationships, IList<ReferencedEntity> 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 @@ namespace Microsoft.PowerFx.Connectors
_warnings = connectorType._warnings;
}
internal DisplayNameProvider DisplayNameProvider
{
get
{
_displayNameProvider ??= new SingleSourceDisplayNameProvider(Fields.Select(field => new KeyValuePair<DName, DName>(new DName(field.Name), new DName(field.DisplayName ?? field.Name))));
return _displayNameProvider;
}
}
private DisplayNameProvider _displayNameProvider;
internal void SetRelationship(SqlRelationship relationship)
{
ExternalTables ??= new List<string>();
ExternalTables.Add(relationship.ReferencedTable);
RelationshipName = relationship.RelationshipName;
ForeignKey = relationship.ReferencedColumnName;
}
internal void AddTabularDataSource(ICdpTableResolver tableResolver, IList<ReferencedEntity> referencedEntities, List<SqlRelationship> sqlRelationships, DName name, string datasetName, ConnectorType connectorType, ServiceCapabilities serviceCapabilities, bool isReadOnly, BidirectionalDictionary<string, string> 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<IExternalTabularDataSource> dataSource = new HashSet<IExternalTabularDataSource>() { 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)
{

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

@ -18,6 +18,7 @@ namespace Microsoft.PowerFx.Connectors
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 @@ namespace Microsoft.PowerFx.Connectors
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";
}
}

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

@ -19,12 +19,22 @@ namespace Microsoft.PowerFx.Connectors
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, ColumnCapabilitiesBase> Properties => _childColumnsCapabilities.Any() ? _childColumnsCapabilities : null;
private readonly Dictionary<string, ColumnCapabilitiesBase> _childColumnsCapabilities;
private Dictionary<string, ColumnCapabilitiesBase> _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<string, ColumnCapabilitiesBase>()
};
private ColumnCapabilities()
{
}
public void AddColumnCapability(string name, ColumnCapabilitiesBase capability)
{
@ -56,7 +66,11 @@ namespace Microsoft.PowerFx.Connectors
return null;
}
return new ColumnCapabilities(new ColumnCapabilitiesDefinition(filterFunctions, propertyAlias, isChoice));
return new ColumnCapabilities(new ColumnCapabilitiesDefinition(filterFunctions)
{
QueryAlias = propertyAlias,
IsChoice = isChoice
});
}
}
}

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

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Microsoft.PowerFx.Core.Utils;
@ -14,26 +15,23 @@ namespace Microsoft.PowerFx.Connectors
[JsonInclude]
[JsonPropertyName(CapabilityConstants.FilterFunctions)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly string[] FilterFunctions;
public IEnumerable<string> 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<string> filterFunction)
{
// ex: lt, le, eq, ne, gt, ge, and, or, not, contains, startswith, endswith, countdistinct, day, month, year, time
FilterFunctions = filterFunction;
}
}
}

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

@ -15,19 +15,22 @@ namespace Microsoft.PowerFx.Connectors
[JsonInclude]
[JsonPropertyName(CapabilityConstants.FilterRequiredProperties)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly List<string> RequiredProperties;
public readonly IList<string> RequiredProperties;
[JsonInclude]
[JsonPropertyName(CapabilityConstants.NonFilterableProperties)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly List<string> NonFilterableProperties;
public readonly IList<string> NonFilterableProperties;
public FilterRestriction(List<string> requiredProperties, List<string> nonFilterableProperties)
public FilterRestriction(IList<string> requiredProperties, IList<string> nonFilterableProperties)
{
Contracts.AssertValueOrNull(requiredProperties);
Contracts.AssertValueOrNull(nonFilterableProperties);
// List of required properties
RequiredProperties = requiredProperties;
// List of non filterable properties
NonFilterableProperties = nonFilterableProperties;
}
}

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

@ -15,9 +15,9 @@ namespace Microsoft.PowerFx.Connectors
[JsonInclude]
[JsonPropertyName(CapabilityConstants.UngroupableProperties)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly List<string> UngroupableProperties;
public readonly IList<string> UngroupableProperties;
public GroupRestriction(List<string> ungroupableProperties)
public GroupRestriction(IList<string> ungroupableProperties)
{
Contracts.AssertValueOrNull(ungroupableProperties);

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

@ -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<string> 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;
}
}

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

@ -16,6 +16,8 @@ namespace Microsoft.PowerFx.Connectors
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;
}
}

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

@ -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 @@ namespace Microsoft.PowerFx.Connectors
[JsonInclude]
[JsonPropertyName(CapabilityConstants.FilterFunctions)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly string[] FilterFunctions;
public readonly IEnumerable<string> FilterFunctions;
public IEnumerable<DelegationOperator> FilterFunctionsEnum => GetDelegationOperatorEnumList(FilterFunctions);
[JsonInclude]
[JsonPropertyName(CapabilityConstants.FilterFunctionSupport)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly string[] FilterSupportedFunctions;
public readonly IEnumerable<string> FilterSupportedFunctions;
public IEnumerable<DelegationOperator> FilterSupportedFunctionsEnum => GetDelegationOperatorEnumList(FilterSupportedFunctions);
[JsonInclude]
[JsonPropertyName(CapabilityConstants.ServerPagingOptions)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string[] ServerPagingOptions => PagingCapabilities.ServerPagingOptions;
public IEnumerable<string> ServerPagingOptions => PagingCapabilities.ServerPagingOptions;
[JsonInclude]
[JsonPropertyName(CapabilityConstants.IsOnlyServerPagable)]
@ -95,8 +103,8 @@ namespace Microsoft.PowerFx.Connectors
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<string> filterFunctions,
IEnumerable<string> filterSupportedFunctions, PagingCapabilities pagingCapabilities, bool recordPermissionCapabilities, int oDataVersion = CurrentODataVersion, bool supportsDataverseOffline = false)
{
Contracts.AssertValueOrNull(sortRestriction);
Contracts.AssertValueOrNull(filterRestriction);
@ -122,16 +130,96 @@ namespace Microsoft.PowerFx.Connectors
SupportsRecordPermission = recordPermissionCapabilities;
}
public static TableDelegationInfo ToDelegationInfo(ServiceCapabilities serviceCapabilities, string tableName, bool isReadOnly, ConnectorType connectorType, string datasetName)
{
SortRestrictions sortRestriction = new SortRestrictions()
{
AscendingOnlyProperties = serviceCapabilities?.SortRestriction?.AscendingOnlyProperties,
UnsortableProperties = serviceCapabilities?.SortRestriction?.UnsortableProperties
};
FilterRestrictions filterRestriction = new FilterRestrictions()
{
RequiredProperties = serviceCapabilities?.FilterRestriction?.RequiredProperties,
NonFilterableProperties = serviceCapabilities?.FilterRestriction?.NonFilterableProperties
};
SelectionRestrictions selectionRestriction = new SelectionRestrictions()
{
IsSelectable = serviceCapabilities?.SelectionRestriction?.IsSelectable ?? false
};
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<string, Core.Entities.ColumnCapabilitiesBase> 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<string, string> 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<DelegationOperator> GetDelegationOperatorEnumList(IEnumerable<string> filterFunctionList)
{
if (filterFunctionList == null)
{
return null;
}
List<DelegationOperator> list = new List<DelegationOperator>();
foreach (string str in filterFunctionList)
{
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);
if (_columnsCapabilities == null)
{
_columnsCapabilities = new Dictionary<string, ColumnCapabilitiesBase>();
}
_columnsCapabilities ??= new Dictionary<string, ColumnCapabilitiesBase>();
_columnsCapabilities.Add(name, capability);
}
@ -160,32 +248,32 @@ namespace Microsoft.PowerFx.Connectors
{
IDictionary<string, IOpenApiAny> 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<string, IOpenApiAny> capabilitiesMetaData)
{
IDictionary<string, IOpenApiAny> sortRestrictionMetaData = capabilitiesMetaData.GetObject(CapabilityConstants.SortRestrictions);
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<string, IOpenApiAny> capabilitiesMetaData)
{
IDictionary<string, IOpenApiAny> 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<string, IOpenApiAny> capabilitiesMetaData)
{
IDictionary<string, IOpenApiAny> 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<string, IOpenApiAny> capabilitiesMetaData)

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

@ -15,19 +15,22 @@ namespace Microsoft.PowerFx.Connectors
[JsonInclude]
[JsonPropertyName(CapabilityConstants.AscendingOnlyProperties)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly List<string> AscendingOnlyProperties;
public readonly IList<string> AscendingOnlyProperties;
[JsonInclude]
[JsonPropertyName(CapabilityConstants.UnsortableProperties)]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public readonly List<string> UnsortableProperties;
public readonly IList<string> UnsortableProperties;
public SortRestriction(List<string> unsortableProperties, List<string> ascendingOnlyProperties)
public SortRestriction(IList<string> unsortableProperties, IList<string> ascendingOnlyProperties)
{
Contracts.AssertValueOrNull(unsortableProperties);
Contracts.AssertValueOrNull(ascendingOnlyProperties);
// List of properties which support ascending order only
AscendingOnlyProperties = ascendingOnlyProperties;
// List of unsortable properties
UnsortableProperties = unsortableProperties;
}
}

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

@ -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; }
}
}

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

@ -8,6 +8,7 @@ using System.Net.Http;
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 @@ namespace Microsoft.PowerFx.Connectors
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 @@ namespace Microsoft.PowerFx.Connectors
Logger = logger;
}
public async Task<CdpTableDescriptor> ResolveTableAsync(string tableName, CancellationToken cancellationToken)
public async Task<ConnectorType> ResolveTableAsync(string tableName, CancellationToken cancellationToken)
{
// out string name, out string displayName, out ServiceCapabilities tableCapabilities
cancellationToken.ThrowIfCancellationRequested();
@ -56,41 +54,40 @@ namespace Microsoft.PowerFx.Connectors
string text = await CdpServiceBase.GetObject(_httpClient, $"Get table metadata", uri, null, cancellationToken, Logger).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(text))
if (string.IsNullOrWhiteSpace(text))
{
List<SqlRelationship> sqlRelationships = null;
// 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);
}
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);
return new CdpTableDescriptor() { ConnectorType = ct, Name = name, DisplayName = displayName, TableCapabilities = tableCapabilities };
return null;
}
return new CdpTableDescriptor();
List<SqlRelationship> sqlRelationships = null;
// 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);
}
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/");

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

@ -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<string, string> 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<string, string>();
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<string, string> DisplayNameMapping { get; protected set; }
public BidirectionalDictionary<string, string> 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<string> GetKeyColumns()
{
return /*TableMetadata?.KeyColumns ??*/ new List<string>();
}
public IEnumerable<string> GetKeyColumns(IExpandInfo expandInfo)
{
throw new NotImplementedException();
}
}
}

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

@ -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 @@ namespace Microsoft.PowerFx.Connectors
{
ConnectorLogger Logger { get; }
[Obsolete("This property is a temporary hack to generate ADS")]
bool GenerateADS { get; init; }
Task<CdpTableDescriptor> ResolveTableAsync(string tableName, CancellationToken cancellationToken);
Task<ConnectorType> ResolveTableAsync(string tableName, CancellationToken cancellationToken);
}
}

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

@ -32,7 +32,7 @@ namespace Microsoft.PowerFx.Connectors
return await GetObject<DatasetMetadata>(httpClient, "Get datasets metadata", uri, null, cancellationToken, logger).ConfigureAwait(false);
}
public async Task<IEnumerable<CdpTable>> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
public virtual async Task<IEnumerable<CdpTable>> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
{
if (DatasetMetadata == null)
{
@ -47,7 +47,7 @@ namespace Microsoft.PowerFx.Connectors
+ (uriPrefix.Contains("/sharepointonline/") ? "/alltables" : "/tables");
GetTables tables = await GetObject<GetTables>(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 });
}
/// <summary>
@ -61,7 +61,7 @@ namespace Microsoft.PowerFx.Connectors
/// <param name="logger">Logger.</param>
/// <returns>CdpTable class.</returns>
/// <exception cref="InvalidOperationException">When no or more than one tables are identified.</exception>
public async Task<CdpTable> GetTableAsync(HttpClient httpClient, string uriPrefix, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null)
public virtual async Task<CdpTable> GetTableAsync(HttpClient httpClient, string uriPrefix, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null)
{
cancellationToken.ThrowIfCancellationRequested();
IEnumerable<CdpTable> tables = await GetTablesAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false);

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

@ -14,30 +14,25 @@ namespace Microsoft.PowerFx.Connectors
{
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<string, Relationship> 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

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

@ -8,13 +8,14 @@ using System.Net.Http;
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 @@ namespace Microsoft.PowerFx.Connectors
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<string, Relationship> Relationships => _relationships;
internal DatasetMetadata DatasetMetadata;
internal CdpTableDescriptor TabularTableDescriptor;
internal ConnectorType TabularTableDescriptor;
internal IReadOnlyCollection<RawTable> Tables;
@ -40,6 +41,8 @@ namespace Microsoft.PowerFx.Connectors
private HttpClient _httpClient;
private IReadOnlyDictionary<string, Relationship> _relationships;
internal CdpTable(string dataset, string table, IReadOnlyCollection<RawTable> tables)
{
DatasetName = dataset ?? throw new ArgumentNullException(nameof(dataset));
@ -55,7 +58,7 @@ namespace Microsoft.PowerFx.Connectors
//// 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 @@ namespace Microsoft.PowerFx.Connectors
_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 @@ namespace Microsoft.PowerFx.Connectors
_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)

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

@ -0,0 +1,209 @@
// 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<string, string> 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<string, string>(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<string, string> _displayNameMapping;
private readonly BidirectionalDictionary<string, string> _previousDisplayNameMapping;
TabularDataQueryOptions IExternalTabularDataSource.QueryOptions => _tabularDataQueryOptions;
public string Name => EntityName.Value;
public DName EntityName { get; }
public bool IsSelectable => _delegationInfo.SelectionRestriction == null ? false : _delegationInfo.SelectionRestriction.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<string, string> IDisplayMapped<string>.DisplayNameMapping => _displayNameMapping;
BidirectionalDictionary<string, string> IDisplayMapped<string>.PreviousDisplayNameMapping => _previousDisplayNameMapping;
public bool HasCachedCountRows => false;
public IReadOnlyList<string> GetKeyColumns() => _externalTableMetadata?.KeyColumns ?? new List<string>();
IEnumerable<string> 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 System.NotImplementedException();
}
bool IExternalTabularDataSource.CanIncludeExpand(IExpandInfo expandToAdd)
{
throw new System.NotImplementedException();
}
bool IExternalTabularDataSource.CanIncludeExpand(IExpandInfo parentExpandInfo, IExpandInfo expandToAdd)
{
throw new System.NotImplementedException();
}
private static List<OperationCapabilityMetadata> GetCapabilityMetadata(AggregateType recordType, TableDelegationInfo delegationInfo)
{
DType type = recordType._type;
DPath GetDPath(string prop) => DPath.Root.Append(new DName(prop));
void AddOrUpdate(Dictionary<DPath, DelegationCapability> 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<DPath, DelegationCapability> groupByRestrictions = new Dictionary<DPath, DelegationCapability>();
if (delegationInfo?.GroupRestriction?.UngroupableProperties != null)
{
foreach (string ungroupableProperty in delegationInfo.GroupRestriction.UngroupableProperties)
{
AddOrUpdate(groupByRestrictions, ungroupableProperty, DelegationCapability.Group);
}
}
Dictionary<DPath, DelegationCapability> sortRestrictions = new Dictionary<DPath, DelegationCapability>();
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);
}
}
Dictionary<DPath, DPath> oDataReplacements = new Dictionary<DPath, DPath>();
FilterOpMetadata filterOpMetadata = new CdpFilterOpMetadata(recordType, delegationInfo);
GroupOpMetadata groupOpMetadata = new GroupOpMetadata(type, groupByRestrictions);
ODataOpMetadata oDataOpMetadata = new ODataOpMetadata(type, oDataReplacements);
SortOpMetadata sortOpMetadata = new SortOpMetadata(type, sortRestrictions);
return new List<OperationCapabilityMetadata>()
{
filterOpMetadata,
groupOpMetadata,
oDataOpMetadata,
sortOpMetadata
};
}
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));
}
}
}
}

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

@ -7,9 +7,9 @@ using Microsoft.PowerFx.Core.Functions.Delegation;
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 @@ namespace Microsoft.PowerFx.Connectors
public IExternalTableDefinition EntityDefinition => throw new NotImplementedException();
public CdpDataSourceMetadata(string entityName, string datasetName, BidirectionalDictionary<string, string> displayNameMapping = null)
public InternalDataEntityMetadata(string entityName, string datasetName, BidirectionalDictionary<string, string> displayNameMapping = null)
{
EntityName = entityName;
DatasetName = datasetName;

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

@ -5,19 +5,19 @@ using System.Collections.Generic;
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<string, IDataEntityMetadata> _entityIdToMetadataMap;
public CdpEntityMetadataProvider()
public InternalDataEntityMetadataProvider()
{
_entityIdToMetadataMap = new Dictionary<string, IDataEntityMetadata>();
}
public void AddSource(string sourceName, IDataEntityMetadata tabularDataSource)
{
{
_entityIdToMetadataMap.Add(sourceName, tabularDataSource);
}

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

@ -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<string> KeyColumns => _type._type.DisplayNameProvider.LogicalToDisplayPairs.Select(pair => pair.Key.Value).Where(col => col != "57dfb1b5-7d79-4046-a4da-fd831d5befe1-KeyId").ToList();
public IReadOnlyList<ColumnMetadata> 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;
}
/// <summary>
/// Checks whether specified column can be included in select query option.
/// </summary>
/// <param name="selectColumnName"></param>
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;
}
/// <summary>
/// Checks whether specified navigation column can be included in expand query option.
/// </summary>
/// <param name="expand"></param>
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<user/team>'.
return colType.PolymorphicInfo.TargetFields.Contains("ownerid") && (expand.Name == "owninguser" || expand.Name == "owningteam");
}
}
}

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

@ -0,0 +1,289 @@
// 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
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<DelegationOperator> FilterFunctions { get; init; }
// Filter functions supported by the table
public IEnumerable<DelegationOperator> FilterSupportedFunctions { get; init; }
// Defines paging capabilities
internal PagingCapabilities PagingCapabilities { get; init; }
// Defining per column capabilities
internal IReadOnlyCollection<KeyValuePair<string, ColumnCapabilitiesBase>> 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; }
// Dataset name
public string DatasetName { get; init; }
// Defines columns with relationships
// Key = field logical name, Value = foreign table logical name
internal Dictionary<string, string> ColumnsWithRelationships { get; init; }
public virtual bool IsDelegable => (SortRestriction != null) || (FilterRestriction != null) || (FilterFunctions != null);
public TableDelegationInfo()
{
PagingCapabilities = new PagingCapabilities()
{
IsOnlyServerPagable = false,
ServerPagingOptions = new string[0]
};
SupportsRecordPermission = true;
ColumnsWithRelationships = new Dictionary<string, string>();
}
public abstract ColumnCapabilitiesDefinition GetColumnCapability(string fieldName);
}
internal sealed class ComplexColumnCapabilities : ColumnCapabilitiesBase
{
internal Dictionary<string, ColumnCapabilitiesBase> _childColumnsCapabilities;
public ComplexColumnCapabilities()
{
_childColumnsCapabilities = new Dictionary<string, ColumnCapabilitiesBase>();
}
public void AddColumnCapability(string name, ColumnCapabilitiesBase capability)
{
Contracts.AssertNonEmpty(name);
Contracts.AssertValue(capability);
_childColumnsCapabilities.Add(name, capability);
}
}
public sealed class ColumnCapabilities : ColumnCapabilitiesBase
{
public IReadOnlyDictionary<string, ColumnCapabilitiesBase> Properties => _childColumnsCapabilities.Any() ? _childColumnsCapabilities : null;
private Dictionary<string, ColumnCapabilitiesBase> _childColumnsCapabilities;
private ColumnCapabilitiesDefinition _capabilities;
// Those are default CDS filter supported functions
// From // PowerApps-Client\src\Language\PowerFx.Dataverse.Parser\Importers\DataDescription\CdsCapabilities.cs
public static readonly IEnumerable<DelegationOperator> 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<string, ColumnCapabilitiesBase>()
};
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<string, ColumnCapabilitiesBase>();
}
}
public sealed class ColumnCapabilitiesDefinition
{
public IEnumerable<DelegationOperator> 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<DelegationOperator> _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<string> 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<string> 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<string> RequiredProperties { get; init; }
// List of non filterable properties (like images)
public IList<string> NonFilterableProperties { get; init; }
public FilterRestrictions()
{
}
}
public sealed class SortRestrictions
{
// Columns only supported ASC ordering
public IList<string> AscendingOnlyProperties { get; init; }
// Columns that don't support ordering
public IList<string> UnsortableProperties { get; init; }
public SortRestrictions()
{
}
public SortRestrictions(IList<string> unsortableProperties, IList<string> ascendingOnlyProperties)
{
// List of properties which support ascending order only
AscendingOnlyProperties = ascendingOnlyProperties;
// List of unsortable properties
UnsortableProperties = unsortableProperties;
}
}
}

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

@ -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<Dictionary<BinaryOp, DelegationCapability>> _binaryOpToDelegationCapabilityMap =
new Lazy<Dictionary<BinaryOp, DelegationCapability>>(
() => new Dictionary<BinaryOp, DelegationCapability>
@ -180,6 +184,305 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation
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));
}
return sb.ToString();
}
}
private static void AddCommaIfNeeded(StringBuilder sb)
{
if (sb.Length != 0)
{
sb.Append(", ");
}
}
public static bool IsValid(BigInteger capabilityConstant)
{
return (capabilityConstant == None) ||

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

@ -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;
}
}
}

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

@ -8,36 +8,32 @@ using Microsoft.PowerFx.Core.Utils;
namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
{
// Defines filter operation metadata.
internal sealed class FilterOpMetadata : OperationCapabilityMetadata
internal class FilterOpMetadata : OperationCapabilityMetadata
{
private readonly Dictionary<DPath, DelegationCapability> _columnCapabilities;
private readonly Dictionary<DPath, DelegationCapability> _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;
private readonly DelegationCapability _defaultCapabilities;
public FilterOpMetadata(
DType tableSchema,
Dictionary<DPath, DelegationCapability> columnRestrictions,
Dictionary<DPath, DelegationCapability> columnCapabilities,
DelegationCapability filterFunctionsSupportedByAllColumns,
DelegationCapability? filterFunctionsSupportedByTable)
public FilterOpMetadata(DType tableSchema, Dictionary<DPath, DelegationCapability> columnRestrictions, Dictionary<DPath, DelegationCapability> columnCapabilities, DelegationCapability filterFunctionsSupportedByAllColumns, DelegationCapability? filterFunctionsSupportedByTable)
: base(tableSchema)
{
Contracts.AssertValue(columnRestrictions);
Contracts.AssertValue(columnCapabilities);
{
_columnCapabilities = columnCapabilities;
_columnRestrictions = columnRestrictions;
_filterFunctionsSupportedByTable = filterFunctionsSupportedByTable;
_defaultCapabilities = filterFunctionsSupportedByAllColumns;
if (_filterFunctionsSupportedByTable != null)
{
_defaultCapabilities = filterFunctionsSupportedByAllColumns | DelegationCapability.Filter;
}
}
}
protected override Dictionary<DPath, DelegationCapability> ColumnRestrictions => _columnRestrictions;
@ -55,7 +51,7 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
{
// 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 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
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))

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

@ -52,4 +52,48 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation
public const string AsType = "astype";
public const string ArrayLookup = "arraylookup";
}
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
}
}

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

@ -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 @@ namespace Microsoft.PowerFx.Types
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)

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

@ -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 @@ namespace Microsoft.PowerFx.Types
/// Derived classes calling this must override <see cref="AggregateType.FieldNames"/>
/// and <see cref="AggregateType.TryGetFieldType(string, out FormulaType)"/>.
/// </summary>
/// <param name="displayNameProvider">Provide DispayNamerovide to be used.</param>
/// <param name="displayNameProvider">Provide DisplayNameProvider to be used.</param>
public RecordType(DisplayNameProvider displayNameProvider)
: base(false, displayNameProvider)
{
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RecordType"/> class with <see cref="DisplayNameProvider"/> and <see cref="TableDelegationInfo"/>.
/// Derived classes calling this must override <see cref="AggregateType.TryGetFieldType(string, out FormulaType)"/>.
/// </summary>
/// <param name="displayNameProvider">Provide DisplayNameProvider to be used.</param>
/// <param name="delegationInfo">Table provider to be used.</param>
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<string> FieldNames => _fieldNames;
private readonly IEnumerable<string> _fieldNames = null;
public override void Visit(ITypeVisitor vistor)
{
vistor.Visit(this);

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

@ -239,7 +239,7 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
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);
}

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

@ -46,7 +46,7 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
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);
}

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

@ -515,7 +515,7 @@ namespace Microsoft.PowerFx.Core.Types
AssertValid();
}
internal DType(LazyTypeProvider provider, bool isTable, DisplayNameProvider displayNameProvider = null)
internal DType(LazyTypeProvider provider, bool isTable, DisplayNameProvider displayNameProvider = null, HashSet<IExternalTabularDataSource> associatedDataSources = null)
{
Contracts.AssertValue(provider);
@ -528,7 +528,7 @@ namespace Microsoft.PowerFx.Core.Types
ExpandInfo = null;
PolymorphicInfo = null;
Metadata = null;
AssociatedDataSources = new HashSet<IExternalTabularDataSource>();
AssociatedDataSources = associatedDataSources ?? new HashSet<IExternalTabularDataSource>();
OptionSetInfo = null;
ViewInfo = null;
NamedValueKind = null;
@ -1069,7 +1069,7 @@ namespace Microsoft.PowerFx.Core.Types
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 @@ namespace Microsoft.PowerFx.Core.Types
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:

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

@ -3,8 +3,6 @@
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.PowerFx.Core.Functions.Delegation;
using Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata;
using Microsoft.PowerFx.Core.Types;
using Microsoft.PowerFx.Core.Utils;
@ -13,9 +11,12 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
internal sealed partial class DelegationMetadata : DelegationMetadataBase
{
public DelegationMetadata(DType schema, string delegationMetadataJson)
: base(
schema: schema,
compositeMetadata: new DelegationMetadataParser().Parse(delegationMetadataJson, schema))
: base(schema, new DelegationMetadataParser().Parse(delegationMetadataJson, schema))
{
}
public DelegationMetadata(DType schema, List<OperationCapabilityMetadata> metadata)
: base(schema, new CompositeCapabilityMetadata(schema, metadata))
{
}

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

@ -54,7 +54,7 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
if (propertyCapabilityJsonObject.TryGetProperty(CapabilitiesConstants.PropertyQueryAlias, out var alias))
{
oDataReplacement.Add(propertyPath, GetReplacementPath(alias.GetString(), columnPath));
oDataReplacement.Add(propertyPath, Entities.DataSourceInfo.GetReplacementPath(alias.GetString(), columnPath));
}
}
}
@ -62,26 +62,6 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
return new ODataOpMetadata(schema, oDataReplacement);
}
private 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));
}
}
}
}
}
}

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

@ -246,9 +246,9 @@ namespace Microsoft.PowerFx.Types
{
var name = pair.Name;
var value = pair.Value;
FormulaType fieldType = null;
FormulaType fieldType = null;
if (recordType?.TryGetFieldType(name, out fieldType) == false)
if (recordType?.TryGetUnderlyingFieldType(name, out fieldType) == false)
{
if (!settings.AllowUnknownRecordFields)
{

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

@ -33,22 +33,22 @@ namespace Microsoft.PowerFx.Tests
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<object>("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<object>("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<object>("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);
}
}
}

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

@ -8,6 +8,8 @@ using System.Linq;
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 @@ namespace Microsoft.PowerFx.Connectors.Tests
// 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 @@ namespace Microsoft.PowerFx.Connectors.Tests
StringValue str = Assert.IsType<StringValue>(result);
Assert.Equal("b", str.Value);
RecordType trt = fileTable.TabularRecordType;
RecordType trt = fileTable.RecordType;
Assert.NotNull(trt);
}
}
@ -73,17 +77,17 @@ namespace Microsoft.PowerFx.Connectors.Tests
_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<string, Relationship> 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<IReadOnlyCollection<DValue<RecordValue>>> GetItemsInternalAsync(IServiceProvider serviceProvider, ODataParameters oDataParameters, CancellationToken cancellationToken)
@ -99,4 +103,45 @@ namespace Microsoft.PowerFx.Connectors.Tests
return lines.Select(line => DValue<RecordValue>.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<Core.Utils.DName, Core.Utils.DName>(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();
}
}
}

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

@ -152,6 +152,12 @@ namespace Microsoft.PowerFx.Connectors.Tests
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));
}

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

@ -2000,52 +2000,57 @@ POST https://tip1-shared-002.azure-apim.net/invoke
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]]]";
"!['@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<object>(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);

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

@ -15,6 +15,8 @@ using Microsoft.PowerFx.Types;
using Xunit;
using Xunit.Abstractions;
#pragma warning disable SA1116
namespace Microsoft.PowerFx.Connectors.Tests
{
public class PowerPlatformTabularTests
@ -97,40 +99,26 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<IExternalTabularDataSource> 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<TabularDataSource>(ads.First());
Assert.NotNull(tds);
Assert.NotNull(tds.DataEntityMetadataProvider);
DataSourceInfo dataSourceInfo = Assert.IsType<DataSourceInfo>(ads.First());
Assert.NotNull(dataSourceInfo);
CdpEntityMetadataProvider cemp = Assert.IsType<CdpEntityMetadataProvider>(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<TabularDataSourceMetadata>(dem);
Assert.Equal("pfxdev-sql.database.windows.net,connectortest", tdsm.DatasetName);
Assert.Equal("Customers", tdsm.EntityName);
Assert.Equal("Customers", dataSourceInfo.Name);
Assert.True(dataSourceInfo.RequiresAsync);
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.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<ConnectorLogger>(logger);
@ -224,13 +212,15 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<object>("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<IExternalTabularDataSource> 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<ConnectorLogger>(logger);
@ -247,13 +237,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
StringValue address = Assert.IsType<StringValue>(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("[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<CdpRecordType>(productModelID);
@ -262,7 +252,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
// 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<object>("r![CatalogDescription:s, ModifiedDate:d, Name:s, ProductModelID:~[SalesLT].[ProductModel]:w, rowguid:s]", productModelRecordType.ToStringWithDisplayNames()); // Logical Name
}
[Fact]
@ -291,7 +281,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
Assert.True(sapTable.IsInitialized);
CdpTableValue sapTableValue = sapTable.GetTableValue();
Assert.Equal<object>("*[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<object>(
"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";
@ -341,7 +333,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<object>("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<ConnectorLogger>(logger);
@ -418,13 +410,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<object>(
"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<IExternalTabularDataSource> ads = spTable.Type._type.AssociatedDataSources;
Assert.NotNull(ads);
@ -455,13 +447,11 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<ConnectorLogger>(logger);
@ -508,13 +498,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<object>(
"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<ConnectorLogger>(logger);
@ -660,22 +650,22 @@ namespace Microsoft.PowerFx.Connectors.Tests
// 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<object>(
"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<IExternalTabularDataSource> ads = sfTable.Type._type.AssociatedDataSources;
Assert.NotNull(ads);
@ -707,73 +697,76 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<CdpRecordType>(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<object>(
"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 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<object>(
"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<ConnectorLogger>(logger);
@ -897,9 +890,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
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<ConnectorLogger>(logger);
@ -968,9 +961,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
Assert.True(zdTable.IsDelegable);
Assert.Equal(
"![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, " +
"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.TabularRecordType).ToStringWithDisplayNames());
"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<ConnectorLogger>(logger);

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

@ -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": [

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

@ -21,7 +21,8 @@ namespace Microsoft.PowerFx.Connectors.Tests
var bugsFieldType = new HashSet<Type>();
var bugNames = new HashSet<string>()
{
"ConnectorFunction._slash"
"ConnectorFunction._slash",
"ColumnCapabilities.DefaultFilterFunctionSupport"
};
AnalyzeThreadSafety.CheckStatics(asm, bugsFieldType, bugNames);

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

@ -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<object>(enums, constants);
}
}
public class TestRecordType : RecordType
{
private readonly RecordType _recordType;
private readonly List<string> _allowedFilters;
public TestRecordType(string tableName, RecordType recordType, List<string> 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<DName, DName>(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>() { DelegationOperator.Eq }
};
}
return null;
}
}
}

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

@ -36,19 +36,14 @@ namespace Microsoft.PowerFx.Core.Tests
}
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 @@ namespace Microsoft.PowerFx.Core.Tests
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 @@ namespace Microsoft.PowerFx.Core.Tests
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 @@ namespace Microsoft.PowerFx.Core.Tests
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 @@ namespace Microsoft.PowerFx.Core.Tests
}
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 @@ namespace Microsoft.PowerFx.Core.Tests
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 @@ namespace Microsoft.PowerFx.Core.Tests
}
sb.Append(":");
sb.AppendToWithDisplayNames(kvp.Value);
sb.AppendToWithDisplayNames(kvp.Value, null);
strPre = ", ";
}
@ -166,7 +229,7 @@ namespace Microsoft.PowerFx.Core.Tests
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);

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

@ -47,7 +47,6 @@ namespace Microsoft.PowerFx.Core.Tests
"Microsoft.PowerFx.ParseResult",
"Microsoft.PowerFx.ParserOptions",
"Microsoft.PowerFx.IPostCheckErrorHandler",
"Microsoft.PowerFx.EngineDocumentation",
// Config & Symbols
@ -130,11 +129,11 @@ namespace Microsoft.PowerFx.Core.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",
@ -144,7 +143,7 @@ namespace Microsoft.PowerFx.Core.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",
@ -193,18 +192,27 @@ namespace Microsoft.PowerFx.Core.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();